Les classes.
 

Jusqu'à maintenant nous avons seulement utilisé la programmation fonctionnelle. Mais Python offre la possibilité de programmer en orienté objet.

La programmation orientée objet permet un niveau d'abstraction plus fin. On considère qu'un langage est orienté objet lorsqu'il supporte au moins les trois mécanismes suivant :
 

L'encapsulation : ce mécanisme permet de mettre en arrière plan les données d'un objet, en mettant plutôt en en avant le comportement de cet objet.
 

L'Héritage : Cette relation permet de créer de nouveaux objets, et méthodes, en utilisant les caractéristiques de classes déjà existantes.
 

Polymorphisme : le polymorphisme est un mécanisme qui permet à une méthode d'agir différemment en fonction du type d'objet qui est la cible de l'opération.
 

Un peu de vocabulaire.
 

Classe : Objet ou instruction définissant des membres et/ou des méthodes ! Une classe se crée avec le mot réservé class.
 

Instance : Attention ce mot dans le contexte de la programmation orienté objet prend la définition du mot anglais qui signifie : cas particulier, ou exemple. Et non le mot français qui signifie : prière, demande urgente !

Bref une instance est un objet créé à partir d'une classe, donc elle hérite de ces membres et de ses méthodes.

 

Membre : Attribut d'une classe ou d'une de ses instances.
 

Méthode : Attribut d'une classe qui est associé à une fonction. Une méthode se crée avec le mot réservé def (semblable à une fonction)
 

Self : Nom donné conventionnellement à l'instance d'une classe dans la définition de ses méthodes.
 

Héritage : Mécanisme permettant à une instance de bénéficier des membres et des méthodes d'autres classes. La classe enfant (ou sous classe) est la classe qui hérite de membres et de méthodes d'une classe parent (ou super classe).
 
 

La surcharge des opérateurs :
 

À travers une méthode qui porte un nom spécifique ( en général __xxx__) il est possible avec Python d'intercepter certaines fonctions. En effet quand une instance de classe utilise une opération associée Python appelle automatiquement la méthode appartenant à la classe de l'instance.

Voilà à quoi peut servir la surcharge des opérateurs :
 

La surcharge des opérateurs permet aux classes d'intercepter les opérations normales de Python.
 

Les classes peuvent surcharger tous les opérateurs des expressions de Python.
 

Les classes peuvent aussi surcharger les opérations associées aux objets.
 

La surcharge permet aux instances de classes d'agir de façon plus proche des types intégrés.
 

La surcharge des opérateurs est implémentée en fournissant des méthodes de classes nommées spécialement.
 

Exercice sur les classes.
 

Pour comprendre les classes nous allons étudier un exemple permettant de suivre les différentes possibilités de ces objets. Une classe est un modèle sur lequel des instances en deviennent des cas particuliers !
 

Définition d'une classe.
 

Pour notre exemple, nous allons prendre une classe qui prend en compte les membres d'une équipe de basket. Notre classe est le profil de base d'un joueur de basket avec comme paramètres, le numéro du maillot, le nombre de points marqués durant la saison, et le nombre de passes décisives (passe qui aboutit par un panier) durant la saison.

Notre classe "Joueur" devient :
 
 

>>> class Joueur:

...             def __init__(self, num="x", pts=0, passe=0):

...                 self.num = num

...                 self.pts = pts

...                 self.passe = passe
 
 

Dans cette première définition nous avons une classe qui comporte une méthode. Cette méthode __init__ permet lors de l'affectation de prendre les arguments et d'en faire des membres de l'instance.

Nous allons rajouter dans notre class une méthode permettant d'évaluer la performance du joueur, ne retournant rien, mais affichant une note allant de 1 à 5  par rapport au nombre de points !:
 

# Fichier joueur.py by j.tschanz

# C'est une classe simple !
 

class Joueur:
 

    def __init__(self, num="x", pts=0, passe=0):

        self.num = num

        self.pts = pts

        self.passe = passe
 

# passge d'arguments à membres de l'instance.
 

def note(self):

    if self.pts < 30:

        return(1)

    elif self.pts < 60:

        return(2)

    elif self.pts < 80:

        return(3)

    elif self.pts < 100:

        return(4)

    elif self.pts > 100:

        return(5)

    else:

        pass
# fin de la class Joueur et du fichier joueur.py

 

Maintenant nous allons évaluer les performances de nos joueurs en les déclarants instances (cas particulier !) de la class Joueur :
 

# Fichier equipe.py by j.tschanz

# Les joueurs

from joueur import *

jack = Joueur("23", 84, 6) # Création de l'instance

bob = Joueur("11", 125, 2)

luc = Joueur("2", 34, 10)

mark = Joueur("45", 18, 34)

billy = Joueur("98", 10, 2)
 

print "Les performances"

print "Jack : %s" % (jack.note()) # Affichage de la note

print "Bob : %s" % (bob.note())

print "Luc : %s" % (luc.note())

print "Mark : %s" % (mark.note())

print "Billy : %s" % (billy.note())
 

# fin de equipe.py
 

La sortie de l'évaluation donne :
 
 


Maintenant les joueurs sont des instances de la classe Joueur, et les numéros de maillot, le nombre de points et le nombre de passes sont des membres de cette instance.

Pour afficher un membre d'une instance il suffit d'inscrire le nom de l'instance suivit par le nom du membre, les deux étant séparés par un point.

 

>>> jack.pts

84

>>> jack.num

'23'

>>> jack.passe

6

>>> jack

<joueur.Joueur instance at 668c100>

>>> dir(jack)

['num', 'passe', 'pts']

>>>
 

L'héritage des classes.
 

Jusque là nous avons vu un exemple simple d'encapsulation des classes, maintenant toujours en suivant notre exemple nous allons faire un héritage de classe.

En effet notre joueur Mark na pas de bonnes performances car il est passeur et par conséquent ne marque pas beaucoup de points. Nous allons créer une nouvelle classe qui sera une classe enfant de la classe Joueur (donc Joueur sera la classe parent !), notre fichier de classe devient :
 

# Fichier joueur.py by j.tschanz

# C'est une classe simple !

class Joueur:
 

    def __init__(self, num="x", pts=0, passe=0):

        self.num = num

        self.pts = pts

        self.passe = passe

# passge d'arguments à membres de l'instance.

    def note(self):

        if self.pts < 30:

            return(1)

        elif self.pts < 60:

            return(2)

        elif self.pts < 80:

            return(3)

        elif self.pts < 100:

            return(4)

        elif self.pts > 100:

            return(5)

        else:

            pass
 

# ********************************************
 

class Passeur(Joueur):

# classe Passeur avec comme parent Joueur.

    def note(self):

        if self.passe < 15:

            return("faible")

        elif self.passe < 30:

            return("moyen")

        elif self.pass > 30:

            return("bien")

        else:

            pass

# fin de la class Joueur et du fichier joueur.py
 

Il faut savoir qu'une classe enfant peut avoir plusieurs classes parents, comme vous l'avez compris les classes parent se trouvent dans la parenthèse de la ligne de définition de la classe.

L'ordre de hiérarchie va de gauche à droite donc si :
 

>>> class A(B, C)
 

La classe A est enfant de B avant d'être enfant de C, se qui veut dire que les méthodes seront cherchée d'abord dans A puis B et si elles ne s'y trouvent pas ensuite dans C. Les méthodes entre enfants et parents peuvent porter le même nom !

Depuis là notre joueur pourra avoir son propre barème d'evaluation, mais pour cela il faut encore changer son affectation dans le fichier de base :
 

# Fichier equipe.py by j.tschanz

# Les joueurs

from joueur import *

jack = Joueur("23", 84, 6)

bob = Joueur("11", 125, 2)

luc = Joueur("2", 34, 10)

mark = Passeur("45", 18, 34) # Changement d'affectation.

billy = Joueur("98", 10, 2)
 

print "Les performances"

print "Jack : %s" % (jack.note())

print "Bob : %s" % (bob.note())

print "Luc : %s" % (luc.note())

print "Mark : %s" % (mark.note())

print "Billy : %s" % (billy.note())
 

# fin de equipe.py
 

La sortie est :
 
 


Et voilà notre classe Passeur a bien fonctionné, tous les arguments ont été traités avec __init__ de la classe Joueur, mais la méthode note à été celle de la classe Passeur.
 
 

Les retours possible avec une classe.
 

Nous avons vu jusqu'à maintenant l'encapsulation des classes qui permettent de ne s'occuper que de l'objet et non de ces membres. Nous avons aussi vu la possibilité de faire des sous-classes reprenant les méthodes de leurs classes parents.

Maintenant nous allons créer dans la classe Joueur une méthode permettant d'additionner les points et le nombre de passes décisives.
 

def total(self, *other):

    x = self.pts

    y = self.passe

    for i in other:

        x = x+i.pts

        y = y+i.passe
 

return("total", x, y)
 

Cette méthode retourne un tuple contenant un nom, le nombre de points et le nombres de passes décisives. Pour calculer le total il suffit après le lancement du programme équipe d'entrer la commande :
 

>>> d = jack.total(bob, luc, mark, billy)

>>> print d

('total', 271, 54)
 
 

"d" n' est qu'une variable, mais il est possible dans un retour d'affecter une classe à une variable. Par exemple si on change la méthode par :
 

def total(self, *other):

    x = self.pts

    y = self.passe

    for i in other:

        x = x+i.pts

        y = y+i.passe
 

    return Joueur("total", x, y) # La variable devient une Instance.
 

La variable `d' deviendra une instance de la classe joueur, il sera donc possible d'appeler la méthode note.
 

>>> d = jack.total(bob, luc, mark, billy)

>>> print d

<joueur.Joueur instance at 663bf00>

>>> d.note()

5

>>> print d.num, d.pts, d.passe

total 271 54

>>>
 
 

La surcharge des opérateurs.
 

Il nous reste à voir la surcharge des opérateurs, en effet il serait plus simple de marquer :

>>> d = jack+bob+billy+mark+luc

Les méthodes spéciales nous permettent de faire cela avec leurs propriété de court-circuit les opérations normalles de Python.
 

def __add__(self, *other):

    x = self.pts

    y = self.passe

    for i in other:

        x = x+i.pts

        y = y+i.passe
 

    return Joueur("total", x, y)
 
 

def __repr__(self):

    return "numéro : %s points : %d passe : %d" % (self.num, self.pts, self.passe)
 
 

Nous avons rajouté deux méthodes : __add__ qui nous permet d'additionner des classes Joueur juste avec l'opérateur `+', quand à __repr__, elle nous permet lors de l'appel de la fonction print de renvoyer un formatage bien précis.
 
 

>>> d = jack + bob + billy + mark + luc

>>> print d

numéro : total points : 271 passe : 54

>>>
 

Voici une liste partielle des méthodes dites spéciales :
 
 

 
Méthodes Surcharge Appelée pour
__init__ Constructeur Création d'objet : Classe() 
__del__ Destructeur Suppression d'objet 
__add__ Opérateur `+' X+Y 
__or__ Opérateur 'l' (ou bit-à-bit) X1Y 
__repr__ Affichage, conversions print X, `X`
__call__ Appels de fonctions X()
__getattr__ Qualification X indéfini
__getitem__ Indiçage X[cle], boucles for, tests in
__setitem__ Affectation par indice X[cle] = valeur
__getslice__ Découpage X[bas:haut]
__len__ Longueur len(X), tests
__cmp__ Comparaison X =Y,X<Y
__radd__ Opérateur '+' de côté droit Non instance + X

 
 

La programmation orientée objet n'est pas une chose simple et beaucoup de livres lui sont consacrés, mais l'évolution du programme de l'équipe de basket montre une bonne partie des capacités de cette façon de programmer.