Présentation python : Épisode 4 ● Les objets : héritage ● Introduction à l'héritage ● Un exemple illustrant l'héritage ● Détails sur l'appel de méthode ● Les différentes manières de structurer les objets ● Les objets : redéfinition des opérateurs ● Étienne Loks et David Mentré - 7 avril 2008
Rappel sur les objets ● Une classe contient méthodes et attributs ● C'est un « moule » pour un objet ● import random class Dice: ”””Un dé à plusieurs faces””” def __init__(self, nombre_faces): self.nombre_faces = nombre_faces def rouler(self): ”””Faire rouler le dé””” return random.randint(1, self.nombre_faces) >>> de_6_face = Dice(6) >>> print de_6_face.rouler() 4 ● Permet d'encapsuler au plus près méthodes et données
Héritage ● Une classe peut « hériter » d'une autre classe ● de ses attributs et de ses méthodes ● Elle en profite pour ajouter ses attributs et méthodes ● class Point: def __init__(self, x, y): self.x = x self.y = y def affiche(self): print "x:%d y:%d" % (self.x, self.y) class PointColore(Point): def colorise(self, couleur): self.couleur = couleur
Héritage : exemple ● Exemple de la classe mère ● >>> p = Point(3,4) >>> p.affiche() x:3 y:4 ● Exemple de la classe fille : même comportement ! ● >>> pc = PointColore(3,4) >>> pc.affiche() x:3 y:4 ● Mais la classe fille a aussi ses propres méthodes ! ● >>> pc.colorise("rouge") >>> pc.couleur 'rouge'
Redéfinition ● Les méthodes de la classe mère peuvent être redéfinies ● class PointColore(Point): def __init__(self, x, y, couleur): self.x = x self.y = y self.couleur = couleur def affiche(self): print "x:%d y:%d couleur:%s" % (self.x, self.y, self.couleur) def colorise(self, couleur): self.couleur = couleur ● >>> pc = PointColore(3,4,'rouge') >>> pc.affiche() x:3 y:4 couleur:rouge ● OU def __init__(self, x, y, couleur): Point.__init__(self, x, y) self.couleur = couleur
Quel utilité ?!? ● Les classes en haut de la hiérarchie (les grands- mères) définissent des interfaces ● Que tout le monde dans la hiérarchie respecte ● Que tout le monde (dans ou hors la hiérarchie) peut utiliser ● => Avantage : quand on change quelque chose, on ne doit pas tout boulverser
Exemple d'asbtraction ● class Interface: def ecrire(self, chaine): assert False class Console(Interface): def ecrire(self, chaine): print chaine class Fichier(Interface): def __init__(self, nom_fichier): self.f = open(nom_fichier, 'w') def ecrire(self, chaine): self.f.write(chaine) ● >>> c = Console() >>> c.ecrire("toto") toto ● >>> f = Fichier('/tmp/toto') >>> f.ecrire("toto") ● Même règle d'appel !
Appel de méthode en détail ● Quand on appelle une méthode toto() ● on la recherche dans la classe de l'objet ● si on la trouve pas, dans les classes parentes de la classe de l'objet, en remontant la hiérarchie ● si vraiment on ne la trouve pas... ben on renvoie une erreur
Appel de méthode : exemple ● class Toto(): def toto(self): print "methode toto de la classe Toto" def titi(self): print "methode titi de la classe Toto" ● class TotoFils(Toto): def titi(self): print "methode titi de la classe TotoFils" ● >>> t = TotoFils() >>> t.titi() methode titi de la classe TotoFils >>> t.toto() methode toto de la classe Toto >>> t.tata() Traceback (most recent call last): File " ", line 1, in AttributeError: TotoFils instance has no attribute 'tata'
Héritage multiple ● On peut avoir plusieurs parents : ● class Fille(A, B, C, D): [...] ● La recherche de méthode s'effectue en profondeur d'abord ● D'abord les méthodes de la classe Fille ● Puis celle de A (et ses parents) ● Puis celle de B (et ses parents) ●....
Programmer « objet » ● Repérer les classes d'objets qui feront le programme ● s'inspirer des objets réels (ex. un compte dans un programme de compta) ● Regrouper ces classes en hiérarchie ou les faire s'utiliser les unes les autres par référence ● Seule l'expérience peut dire comment faire ● => Design Patterns (encore une autre (longue) histoire) !
Références entre objets ● Un objet peut contenir dans un attribut une référence à un autre objet ● class A: def appelle(self): print "Methode appelle() sur un objet de classe A" class B: def __init__(self): self.a = A() def appelle_a(self): self.a.appelle() ● >>> b = B() # on crée un objet de la classe B >>> b.appelle_a() Methode appelle() sur un objet de classe A ● Souvent la structure la plus commune !
Redéfinition des opérateurs ● >>> A, B = Point(1, 1), Point(2, 2) >>> C = A + B Traceback (most recent call last): File " ", line 1, in TypeError: unsupported operand type(s) for +: 'instance' and 'instance' ● Les opérateurs classiques peuvent être redéfinis pour chaque classe permettant ainsi de manipuler les objets crées de façon plus flexible ● >>> class Point: (...)... def __add__(self, point):... return Point(self.x+point.x, self.y+point.y) >>> C = A + B >>> C.affiche() x:3 y:3
Quelques opérateurs pouvant être redéfinis ● Addition : __add__ ● Soustraction : __sub__ ● Égalité : __eq__ ● Conversion en chaîne de caractères : __str__ ● etc. (cf §6.7 dans la documentation des bibliothèques python) ● Attention à bien documenter une surcharge d'opérateur
Conclusion ● Essentiel des objets ● classes : attributs et méthodes locaux à un objet ● héritage : pouvoir redéfinir des méthodes ● Programmation objet = comment bien utiliser les objets ● Pour commencer ● Utilisez des objets déjà existants (par ex. interface graphique)
Pour aller plus loin ● Références bibliographiques (techniques) ● Design Patterns (Gamma et al.) ● Comment structurer les objets entre eux ● Object-Oriented Software Construction (Bertrand Meyer) ● Explique de manière propre la programmation objet pour faire du bon logiciel