You are on page 1of 12

Le Python, c’est bon

Cours 4 : Classes et exceptions

1 Objets classes
On considère ici que vous avez des notions de langage objets. En particulier,
vous savez ce qu’est une classe, un attribut et une méthode. Nous n’aborderons
pas ici les notions de méta-classes (des classes de classes), présentes dans python,
et laissons le soin au lecteur intéressé de lire la documentation présente sur le
site de python.

1.1 Premières définitions


En python, tout est objet, même les classes. Créons donc un objet classe :
class UneClasse(object) :
""" une classe vide
"""
pass
Le mot clef class défini une classe, il doit être suivi par le nom de la classe
puis, entre parenthèses, des classes dont il hérite. Par défaut, toutes les classes
héritent d’object.

help(UneClasse)
print UneClasse

1.2 Variables internes


Python enrichi directement cette classe de plusieurs variables internes (c’est à
dire des variables commençant et terminant par des “ ”. On peut les voir en
utilisant la fonction dir() :

print dir(UneClasse)

La fonction dir est très utile, elle décrit tout ce que contient un objet. On
pourra essayer dir() pour voir tout ce que contient l’interpréteur, ou encore
dir( builtins ) pour voir, entre autres, les fonctions de bases de python.
Intéressons-nous pour l’instant à deux variables internes particulières (nous
verrons la plupart des autres au cours de ce cours) :

1
print UneClasse.__name__ #nom de la classe
print UneClasse.__doc__ #documentation de la classe
print UneClasse.__module__ #module dans lequel la classe a été crée
print UneClasse.__bases__ #parents direct de la classe

Mettez ceci en perspective de l’instruction :


if __name__ == "__main__":
#des instructions

que nous avions utilisée les cours précédents.

1.3 Instances de classes


Créons 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érite


de object, x en est également 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éfinie dans son attribut class :


>>> print x.__class__
<class ’__main__.UneClasse’>
On peut dynamiquement ajouter des attributs à un objet d’une classe que
l’on a défini. Ainsi :

x.titre = "C’est la vie, Cui Cui"

Définit une variable affectée à x. La liste des variables d’un objet est définie
dans le dictionnaire dict .

print x.__dict__
print x.__dict__["titre"]
x.__dict__["titre"] = "Cure toujours"
print x.titre

2
Tout objet python (une classe, un module, ...) possède une variable dict
qui regroupe tout ce que déclare cet objet (on appelle ça un namespace). On
peut s’en servir pour accéder à une variable, la modifier, ou en créer une autre
(la plupart du temps).

print UneClasse.__dict__
x.__dict__["paroles"] = "Oh non non non j’ai bronzé,\
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éthodes
Un des intérêt d’utiliser le formalisme objet est que l’on peut attribuer des
méthodes spécifiques aux aux instances d’une classe. Pour les objets liste que
nous avons manipulés les cours précédents par exemple, la méthode append
permet d’ajouter un élément en fin de liste. Il existe en python trois grandes
familles de méthodes. Les méthodes prédéfinies commençant et finissant par
“ ” (comme init ou len ), les méthodes programmées et les méthodes
de classe.
A part pour les fonctions de classes, le premier paramètre des méthodes est
toujours l’objet qui invoque la fonction. Par convention on le nommera self.
On peut également toujours invoquer une méthode en utilisant la classe elle-
même (le premier argument étant l’objet considéré), 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éthodes prédéfinies


Lors de la création d’un objet, la méthode init est invoquée. Elle existe donc
toujours, même si l’on ne la programme pas explicitement :
dir(UneClasse)

Créons une méthode init pour notre classe :

3
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éthodes prédéfinies sont particulièrement utiles pour l’interaction de


classes entres-elles et avec python. Ainsi :
• len (self) : appelé par la commande len,
• str (self) : appelé par la commande str,

• del (self, attr) : appelé par la commande del,


Implémentez pour notre classe une fonction len qui rend le nombre de car-
actères de son titre.
Vous trouverez ici un certain nombre de méthodes spéciales de classes :
http://www.python.org/doc/2.5.2/ref/customization.html. De façon plus
générale, rendez-vous ici pour tous les renseignements nécessaires : http://www.
python.org/doc/2.5.2/ref/specialnames.html.
Trouvez par exemples les méthodes permettant les comparaisons entres vos
objets (>, <, ==, 6=, . . . ), et comment faire pour que votre objet puisse être
appelé comme une fonction.

2.2 Méthodes programmées


Les méthodes programmées sont toutes les méthodes que vous pouvez implémenter
pour enrichir vos classes. Comme pour les méthodes prédéfinies (qui ne sont
qu’un cas particulier de méthodes programmées) le premier argument de chaque
méthode est self (votre objet).
Implémentez une méthode maj qui rend le titre de votre objet en majuscules.

2.3 Attributs et méthodes de classe


Les attributs et méthodes de classes sont partagés par toutes les instances de
cette classe.
Ainsi si l’on définie la classe :

4
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éfinissez une variable dans un objet ayant le
même nom que la variable de classe, celle-ci sera masquée :
>>> print x.titre
Cure Toujours
>>> x.titre = "Goldorak est mort" #création 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ême 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égiée.
Pour les méthodes de classes, tout se passe comme pour les méthodes d’instances
saut que le premier paramètre n’est plus une instance, mais une classe. Le nom
de ce paramètre est par convention cls. On peut ainsi reprendre l’exemple
précédent en utilisant une méthode 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
changeTitre = classmethod(changeTitre) #creation de la méthode de classe

5
Et là :
>>> 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éthode de classe, c’est la classe de l’objet qui est passée en
paramètre (x. class ), plus l’objet lui-même.

2.4 Méthodes statiques


Les méthodes statiques sont des méthodes déclarées dans des classes, mais qui
se comportent comme des méthodes normales. Le premier paramètre n’a ici
aucune signification particulière.

class UneClasse(object) :
""" une classe en construction
"""
def maMethodeStatique(titre):
print titre
maMethodeStatique = staticmethod(maMethodeStatique)

Pour une méthode 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étés (Setter/getter)


Il est souvent plus clair d’utiliser une affectation directe d’un attribut que de
passer par une fonction. Plutôt que d’écrire :

x.changeReponse("42")
print x.donneReponse()

Il est plus compréhensible d’écrire :

6
x.reponse = 42
print x.reponse
Si les opérations à effectuer sont plus complexes que juste affecter une valeur
à 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çon 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éritage multiple
Python autorise l’héritage multiple et utilise une règle appelée diamond rule
pour régler les conflits.

class A(object):
classe = "A"

class B(object):
classe = "B"

class C(A, B):


pass

7
Pour savoir qui est quoi, on peut utiliser la fonction issubclass() ou
l’attribut de classe bases (les supérieurs directs) ou encore la méthode 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érités


La recherche d’une méthode ou variable partagée par héritage peut être ex-
plicitée 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ère classe


dans cet ordre ayant la propriété requise est retenue. La création de cette liste
suit la règle de la diamond rule. L’exemple suivant montre cette règle 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):
classe = "diamond Rule"

8
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éthode de résolution des conflits.

3.2 Méthodes parents


La façon 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éthode héritée n’est pas appelée directement. Reprenez le


code précédent et supprimez l’appel direct à l’initiation de la class list. Votre
liste n’est pas initialisée.
On peut également utiliser la classe super(). Liser l’aide de cette classe. Je
reprendrai ici l’exemple donné là http://www.python.org/download/releases/
2.2/descrintro/#cooperation. Pour plus de détails, 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)

9
super(C, self).m fonctionne ainsi. On commence par trouver la position
de C dans self. class . mro puis on recherche la première classe après C
qui contient la fonction m. Il est recommandé de n’utiliser la classe super que
pour la surcharge de la même fonction (super(MaClasse, self).maMethode
ne doit utilisé que dans la surcharge de maMethode pour une classe fille de
MaClasse).
Que donne le résultat de m pour les quatre classes ? Pourquoi ?

4 Exceptions
Vous avez sûrement déjà rencontré 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ègles du zen of Python (PEP20) :
Errors should never pass silently.
Unless explicitly silenced.
Il n’est pas nécessaire de tout vérifier au début de chaque fonction sous peine
d’alourdir considérablement votre programme, mais lorsqu’une erreur survient,
il faut la laisser se propager.
La gestion des erreurs s’effectuent en utilisant les mots clés try et except.
try:
print uneVariableNonAffectee
except:
print "une erreur"
La séquence ci-après vous montre un exemple plus général :
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é"

try:
raise NameError, "C’est farfelu comme nom"
except NameError, inst:
print inst

10
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 étant
Exception. On peut l’utiliser pour lever une exception (avec l’aide de la com-
mande raise) ou s’en servir pour créer ses propres exceptions.

try:
raise NameError, "C’est farfelu comme nom"
except Exception, inst:
print inst

try:
raise Exception(’un’, ’deux’, ’et trois zéro’)
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 à 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émentation d’une
classe permettant de manipuler les graphes.
Nous considérerons ici :
• que les graphes sont non orientés,
• que chaque arête peut posséder un attribut (un nombre, ou tout autre
objet).
Pour la compréhension 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 :

• l’existence de paramètres par défaut : lesquels ?

11
• quelle est l’utilité de la fonction iter ? En quoi diffère-t-elle de la
méthode vertices() ?
• À quoi correspond getitem et comment l’utilise-t-on ?
• Que signifie la commande lambda (retenez cette commande, une fois qu’on
a compris comment ça fonctionne on ne peut s’empêcher de l’utiliser
partout) ?
• Pourquoi avoir défini une classe Sommet vide ?

Vous devriez pouvoir trouver toutes les réponses à vos questions dans les
documentations et liens fournis.
Le module graphs.py définie également un algorithme BellmanFord et une
erreur CircuitError. À l’aide de Wikipédia (par exemple) comprenez leurs
utilités.

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és à Graph


Pour la suite de nos aventures, il va falloir étoffer un peu la classe graphe.
En particulier il vous est demandé de coder et de tester les fonctionnalités
suivantes :
• l’ajout et la suppression de sommets (un ou plusieurs en même temps),
• l’ajout et la suppression d’arêtes (une ou plusieurs en même temps),
• le renommage de sommets,

• qu’il soit possible de trouver un chemin entre deux nœuds donnée, et que
ce ce chemin soit le plus court possible.On pourra implémenter une ver-
sion non orienté de l’algorithme de Dijkstra (il faut pour cela que tous
les poids des arêtes soient positif, sinon rendre une erreur) que l’on trou-
vera ici http://fr.wikipedia.org/wiki/Algorithme_de_Dijkstra par
exemple.

12