Le Python, c’est bon
Cours 4 : Classes et exceptions
1
Objets classes
On consid`ere ici que vous avez des notions de langage objets. En particulier, vous savez ce qu’est une classe, un attribut et une m´ethode. Nous n’aborderons pas ici les notions de m´eta-classes (des classes de classes), pr´esentes dans python, et laissons le soin au lecteur int´eress´e de lire la documentation pr´esente sur le site de python.
1.1
Premi`
eres d´
efinitions
En python, tout est objet, mˆeme les classes. Cr´eons donc un objet classe : class UneClasse(object) :
""" une classe vide """
pass
Le mot clef class d´efini une classe, il doit ˆetre suivi par le nom de la classe puis, entre parenth`eses, des classes dont il h´erite. Par d´efaut, toutes les classes h´eritent d’object.
help(UneClasse) print UneClasse
1.2
Variables internes
Python enrichi directement cette classe de plusieurs variables internes (c’est `a dire des variables commen¸cant et terminant par des “ ”. On peut les voir en utilisant la fonction dir() :
print dir(UneClasse)
La fonction dir est tr`es utile, elle d´ecrit tout ce que contient un objet. On pourra essayer dir() pour voir tout ce que contient l’interpr´eteur, ou encore dir( builtins ) pour voir, entre autres, les fonctions de bases de python.
Int´eressons-nous pour l’instant `a deux variables internes particuli`eres (nous verrons la plupart des autres au cours de ce cours) :
print UneClasse.__name__ #nom de la classe
print UneClasse.__doc__ #documentation de la classe
print UneClasse.__module__ #module dans lequel la classe a ´et´e cr´ee print UneClasse.__bases__ #parents direct de la classe
Mettez ceci en perspective de l’instruction : if __name__ == "__main__":
#des instructions
que nous avions utilis´ee les cours pr´ec´edents.
1.3
Instances de classes
Cr´eons une instance de UneCLasse (un objet UneClasse) : x = UneClasse()
help(x) print x print dir(x)
L’objet x est une instance de la classe UneClasse. Comme UneClasse h´erite de object, x en est ´egalement une instance :
>>> isinstance(x, UneClasse) True
>>> isinstance(x, object) True
>>> isinstance(x, list) #n’est pas une instance de list False
La classe d’une instance est d´efinie dans son attribut class : >>> print x.__class__
<class ’__main__.UneClasse’>
On peut dynamiquement ajouter des attributs `a un objet d’une classe que l’on a d´efini. Ainsi :
x.titre = "C’est la vie, Cui Cui"
D´efinit une variable affect´ee `a x. La liste des variables d’un objet est d´efinie dans le dictionnaire dict .
print x.__dict__
print x.__dict__["titre"]
x.__dict__["titre"] = "Cure toujours" print x.titre
Tout objet python (une classe, un module, ...) poss`ede une variable dict qui regroupe tout ce que d´eclare cet objet (on appelle ¸ca un namespace). On peut s’en servir pour acc´eder `a une variable, la modifier, ou en cr´eer une autre (la plupart du temps).
print UneClasse.__dict__
x.__dict__["paroles"] = "Oh non non non j’ai bronz´e,\ mes potes voudront plus me parler," #idem que x.paroles =
Supprimer une variable se fait simplement par l’instruction del : del x.paroles
print x.__dict__
2
M´
ethodes
Un des int´erˆet d’utiliser le formalisme objet est que l’on peut attribuer des m´ethodes sp´ecifiques aux aux instances d’une classe. Pour les objets liste que nous avons manipul´es les cours pr´ec´edents par exemple, la m´ethode append permet d’ajouter un ´el´ement en fin de liste. Il existe en python trois grandes familles de m´ethodes. Les m´ethodes pr´ed´efinies commen¸cant et finissant par “ ” (comme init ou len ), les m´ethodes programm´ees et les m´ethodes de classe.
A part pour les fonctions de classes, le premier param`etre des m´ethodes est toujours l’objet qui invoque la fonction. Par convention on le nommera self. On peut ´egalement toujours invoquer une m´ethode en utilisant la classe elle-mˆeme (le premier argument ´etant l’objet consid´er´e), comme le montre l’exemple suivant :
>>> x = list() >>> x.append(1) >>> x
[1]
>>> list.append(x, "un autre") >>> x
[1, ’un autre’]
2.1
M´
ethodes pr´
ed´
efinies
Lors de la cr´eation d’un objet, la m´ethode init est invoqu´ee. Elle existe donc toujours, mˆeme si l’on ne la programme pas explicitement :
dir(UneClasse)
class UneClasse(object) :
""" une classe en construction """
def __init__(self, letitre="Cure Toujours"): self.titre = letitre
Maintenant : >>> x = UneClasse() >>> print x.titre Cure Toujours
>>> y = UneClasse("Goldorak est mort") >>> print y.titre
Goldorak est mort
Les m´ethodes pr´ed´efinies sont particuli`erement utiles pour l’interaction de classes entres-elles et avec python. Ainsi :
• len (self) : appel´e par la commande len, • str (self) : appel´e par la commande str, • del (self, attr) : appel´e par la commande del,
Impl´ementez pour notre classe une fonction len qui rend le nombre de car-act`eres de son titre.
Vous trouverez ici un certain nombre de m´ethodes sp´eciales de classes : http://www.python.org/doc/2.5.2/ref/customization.html. De fa¸con plus g´en´erale, rendez-vous ici pour tous les renseignements n´ecessaires : http://www. python.org/doc/2.5.2/ref/specialnames.html.
Trouvez par exemples les m´ethodes permettant les comparaisons entres vos objets (>, <, ==, 6=, . . . ), et comment faire pour que votre objet puisse ˆetre appel´e comme une fonction.
2.2
M´
ethodes programm´
ees
Les m´ethodes programm´ees sont toutes les m´ethodes que vous pouvez impl´ementer pour enrichir vos classes. Comme pour les m´ethodes pr´ed´efinies (qui ne sont qu’un cas particulier de m´ethodes programm´ees) le premier argument de chaque m´ethode est self (votre objet).
Impl´ementez une m´ethode maj qui rend le titre de votre objet en majuscules.
2.3
Attributs et m´
ethodes de classe
Les attributs et m´ethodes de classes sont partag´es par toutes les instances de cette classe.
class UneClasse(object) :
""" une classe en construction """
titre = "C’est la vie, Cui Cui" #attribut de classe On peut alors :
>>> x = UneClasse() >>> x.titre
"C’est la vie, Cui Cui" >>> y = UneClasse() >>> y.titre
"C’est la vie, Cui Cui"
>>> UneClasse.titre = "Cure Toujours" >>> print y.titre
Cure Toujours >>> print x.titre Cure Toujours
Attention cependant. Si vous d´efinissez une variable dans un objet ayant le mˆeme nom que la variable de classe, celle-ci sera masqu´ee :
>>> print x.titre Cure Toujours
>>> x.titre = "Goldorak est mort" #cr´eation d’une nouvelle variable ! >>> print x.titre
Goldorak est mort >>> print y.titre Cure Toujours
>>> del x.titre #suppression de la variable de l’instance >>> print x.titre
Cure Toujours
Les variables de classes et d’instances ne sont pas dans le mˆeme namespace (l’un est dans UneClasse. dict , l’autre dans x. dict ). Si Deux noms co¨ıncide, c’est la variable d’instance qui est privil´egi´ee.
Pour les m´ethodes de classes, tout se passe comme pour les m´ethodes d’instances saut que le premier param`etre n’est plus une instance, mais une classe. Le nom de ce param`etre est par convention cls. On peut ainsi reprendre l’exemple pr´ec´edent en utilisant une m´ethode de classe :
class UneClasse(object) :
""" une classe en construction """
titre = "C’est la vie, Cui Cui" #attribut de classe def changeTitre(cls, titre):
cls.titre = titre
Et l`a :
>>> x = UneClasse()>>> y = UneClasse() >>> x.titre
"C’est la vie, Cui Cui" >>> y.titre
"C’est la vie, Cui Cui"
>>> x.changeTitre("Cure toujours") >>> x.titre
’Cure toujours’ >>> y.titre ’Cure toujours’
Pour une m´ethode de classe, c’est la classe de l’objet qui est pass´ee en param`etre (x. class ), plus l’objet lui-mˆeme.
2.4
M´
ethodes statiques
Les m´ethodes statiques sont des m´ethodes d´eclar´ees dans des classes, mais qui se comportent comme des m´ethodes normales. Le premier param`etre n’a ici aucune signification particuli`ere.
class UneClasse(object) :
""" une classe en construction """
def maMethodeStatique(titre): print titre
maMethodeStatique = staticmethod(maMethodeStatique)
Pour une m´ethode statique, qu’on l’appelle via la classe ou un objet n’a aucune importance. >>> x = UneClasse() >>> x.maMethodeStatique("ici") ici >>> UneClasse.maMethodeStatique("ici") ici
2.5
Propri´
et´
es (Setter/getter)
Il est souvent plus clair d’utiliser une affectation directe d’un attribut que de passer par une fonction. Plutˆot que d’´ecrire :
x.changeReponse("42") print x.donneReponse()
x.reponse = 42 print x.reponse
Si les op´erations `a effectuer sont plus complexes que juste affecter une valeur `
a un attribut, python permet de masquer les appels de fonction via la commande properties
class UneClasse(object) :
""" une classe en construction """
def __init__(self, titre = "rien"): self.__titre = titre
self.longueur = len(titre) def gettitre(self):
return self.__titre def settitre(self, titre):
self.__titre = titre self.longueur = len(titre) titre = property(gettitre, settitre)
La variable titre est maintenant utilisable de fa¸con transparente : >>> x = UneClasse()
>>> print x.titre rien
>>> print x.longueur 4
>>> x.titre = "Guerre et Paix" >>> print x.titre
Guerre et Paix >>> print x.longueur 14
3
H´
eritage multiple
Python autorise l’h´eritage multiple et utilise une r`egle appel´ee diamond rule pour r´egler les conflits.
class A(object): classe = "A" class B(object): classe = "B" class C(A, B): pass
Pour savoir qui est quoi, on peut utiliser la fonction issubclass() ou l’attribut de classe bases (les sup´erieurs directs) ou encore la m´ethode de classe subclasses (les successeurs directs) :
>>> A.__bases__ (<type ’object’>,) >>> B.__bases__ (<type ’object’>,) >>> C.__bases__
(<class ’__main__.A’>, <class ’__main__.B’>) >>> A.__subclasses__() [<class ’__main__.C’>] >>> issubclass(C, A) True >>> issubclass(C, object) True
3.1
Attributs h´
erit´
es
La recherche d’une m´ethode ou variable partag´ee par h´eritage peut ˆetre ex-plicit´ee en regardant la variable de classe mro (method resolution order) : >>> C.__mro__
(<class ’__main__.C’>, <class ’__main__.A’>, <class ’__main__.B’>, <type ’object’>)
Cette variable contient l’ordre de parcours des classes. La premi`ere classe dans cet ordre ayant la propri´et´e requise est retenue. La cr´eation de cette liste suit la r`egle de la diamond rule. L’exemple suivant montre cette r`egle en action : class A(object): classe = "A" class B(A): pass class C(object): classe = "C" class D(B,C): pass print D.classe class Cprim(A):
class Dprim(B,Cprim): pass
print Dprim.classe
Lisez le document http://docs.python.org/whatsnew/2.2.html?highlight= diamond%20rule#multiple-inheritance-the-diamond-rule et expliciter cette m´ethode de r´esolution des conflits.
3.2
M´
ethodes parents
La fa¸con la plus simple d’appeler une fonction d’une classe parent (par exemple init ) est de l’appeler directement :
class maListe(list):
def __init__(self, param): list.__init__(self, param) print "ma liste"
>>> x = maListe(range(5)) ma liste
>>> x
[0, 1, 2, 3, 4]
Attention, une m´ethode h´erit´ee n’est pas appel´ee directement. Reprenez le code pr´ec´edent et supprimez l’appel direct `a l’initiation de la class list. Votre liste n’est pas initialis´ee.
On peut ´egalement utiliser la classe super(). Liser l’aide de cette classe. Je reprendrai ici l’exemple donn´e l`a http://www.python.org/download/releases/ 2.2/descrintro/#cooperation. Pour plus de d´etails, lisez cette partie.
class A(object):
def m(self): "save A’s data" class B(A):
def m(self): "save B’s data"; super(B, self).m() class C(A):
def m(self): "save C’s data"; super(C, self).m() class D(B, C):
def m(self): "save D’s data"; super(D, self).m() On a alors :
A.__mro__ == (A, object) B.__mro__ == (B, A, object) C.__mro__ == (C, A, object) D.__mro__ == (D, B, C, A, object)
super(C, self).m fonctionne ainsi. On commence par trouver la position de C dans self. class . mro puis on recherche la premi`ere classe apr`es C qui contient la fonction m. Il est recommand´e de n’utiliser la classe super que pour la surcharge de la mˆeme fonction (super(MaClasse, self).maMethode ne doit utilis´e que dans la surcharge de maMethode pour une classe fille de MaClasse).
Que donne le r´esultat de m pour les quatre classes ? Pourquoi ?
4
Exceptions
Vous avez sˆurement d´ej`a rencontr´e des exceptions lors de vos premiers essais en python :
print uneVariableNonAffectee # une erreur
La gestion des erreurs est cruciale dans tout programme. Rappelez vous une des deux r`egles du zen of Python (PEP20) :
Errors should never pass silently. Unless explicitly silenced.
Il n’est pas n´ecessaire de tout v´erifier au d´ebut de chaque fonction sous peine d’alourdir consid´erablement votre programme, mais lorsqu’une erreur survient, il faut la laisser se propager.
La gestion des erreurs s’effectuent en utilisant les mots cl´es try et except. try:
print uneVariableNonAffectee except:
print "une erreur"
La s´equence ci-apr`es vous montre un exemple plus g´en´eral : x = []
try:
print x[1] except IndexError:
print "L’indice n’existe pas" except (TypeError, NameError):
print "le nom ou le type n’est pas bon" except:
print "les autres erreurs" else:
print "tout s’est bien pass´e" try:
raise NameError, "C’est farfelu comme nom" except NameError, inst:
Comprenez son fonctionnement en utilisant l’aide du manuel disponible ici http://docs.python.org/tutorial/errors.html.
Les exceptions sont des classes comme les autres. La classe de base ´etant Exception. On peut l’utiliser pour lever une exception (avec l’aide de la com-mande raise) ou s’en servir pour cr´eer ses propres exceptions.
try:
raise NameError, "C’est farfelu comme nom" except Exception, inst:
print inst try:
raise Exception(’un’, ’deux’, ’et trois z´ero’) except Exception, inst:
print inst
class ErreurDeLaNature(Exception): def __init__(self, val):
self.commentaire = val def __str__(self):
return repr(self.commentaire) try:
raise ErreurDeLaNature("et c’est pas joli `a voir") except ErreurDeLaNature, val:
print "Une erreur de la nature arrive", val
5
Exercice
Les fichiers graph.py et test graphs.py contiennent une impl´ementation d’une classe permettant de manipuler les graphes.
Nous consid´ererons ici :
• que les graphes sont non orient´es,
• que chaque arˆete peut poss´eder un attribut (un nombre, ou tout autre objet).
Pour la compr´ehension de la classe, on pourra lire le lien suivant : http: //www.python.org/doc/essays/graphs.html.
5.1
La classe Graph
C’est votre premier exemple de code python que vous n’avez pas fait. Essayer de comprendre sa signification. En particulier :
• quelle est l’utilit´e de la fonction iter ? En quoi diff`ere-t-elle de la m´ethode vertices() ?
• `A quoi correspond getitem et comment l’utilise-t-on ?
• Que signifie la commande lambda (retenez cette commande, une fois qu’on a compris comment ¸ca fonctionne on ne peut s’empˆecher de l’utiliser partout) ?
• Pourquoi avoir d´efini une classe Sommet vide ?
Vous devriez pouvoir trouver toutes les r´eponses `a vos questions dans les documentations et liens fournis.
Le module graphs.py d´efinie ´egalement un algorithme BellmanFord et une erreur CircuitError. A l’aide de Wikip´` edia (par exemple) comprenez leurs utilit´es.
5.2
Les tests
Assurez-vous que les tests sont suffisants et que toutes les lignes sont parcourus dans les tests.
5.3
Ajout de fonctionnalit´
es `
a Graph
Pour la suite de nos aventures, il va falloir ´etoffer un peu la classe graphe. En particulier il vous est demand´e de coder et de tester les fonctionnalit´es suivantes :
• l’ajout et la suppression de sommets (un ou plusieurs en mˆeme temps), • l’ajout et la suppression d’arˆetes (une ou plusieurs en mˆeme temps), • le renommage de sommets,
• qu’il soit possible de trouver un chemin entre deux nœuds donn´ee, et que ce ce chemin soit le plus court possible.On pourra impl´ementer une ver-sion non orient´e de l’algorithme de Dijkstra (il faut pour cela que tous les poids des arˆetes soient positif, sinon rendre une erreur) que l’on trou-vera ici http://fr.wikipedia.org/wiki/Algorithme_de_Dijkstra par exemple.