5 Introduction À La Programmation Objet en Python
5 Introduction À La Programmation Objet en Python
5 Introduction À La Programmation Objet en Python
DAFS
—
Introduction à la programmation objet en Python
Xavier Crégut
<Pré[email protected]>
ENSEEIHT
Sciences du Numérique
Objectifs de ce support :
une introduction à la programmation objet
en l’illustrant avec le langage Python
et le diagramme de classe de la notation UML (Unified Modelling Language)
Principaux éléments :
Exemple introductif : passage de l’impératif à une approche objet
Encapsulation : classe, objets, attributs, méthodes, etc.
Relations entre classes
Relations d’utilisation
Relation d’héritage
Quelques éléments spécifiques à Python
Introspection
Méthodes spéciales et redéfinitions des opérateurs
Métaclasses
Sommaire
1 Exemple introductif
2 Classes et objets
4 Méthodes spéciales
5 Compléments
Modéliser un robot
Exercice 1 Modéliser un robot capable d’avancer d’une case et de pivoter de 90˚ vers la
droite. On pourra alors le guider de la salle de cours (position initiale du robot) jusqu’au
secrétariat.
Secrétariat
Robot
Et le résultat :
r1 = Robot(x=4, y=10, direction=est)
r2 = Robot(x=15, y=7, direction=sud)
r1 = Robot(x=4, y=10, direction=sud)
r2 = Robot(x=15, y=6, direction=sud)
Principe : Un type (prédéfini ou utilisateur) n’a que peu d’intérêt s’il n’est pas équipé
d’opérations !
Justifications :
éviter que chaque utilisateur du type réinvente les opérations
favoriser la réutilisation
Module : Construction syntaxique qui regroupe des types et les opérations associées
Intérêt des modules :
structurer l’application à un niveau supplémentaire par rapport aux sous-programmes (conception
globale du modèle en V) ;
factoriser/réutiliser le code (type et SP) entre applications ;
améliorer la maintenance : une évolution dans l’implantation d’un module ne devrait pas avoir
d’impact sur les autres modules d’une application ;
faciliter le test : le modules sont testés individuellement.
Version objet
Classes et objets
Remarque : On ne sait pas dans quel ordre les opérations seront appelées... mais il faut
commencer par initialiser l’objet : c’est le rôle du constructeur (initialiser).
def avancer(self):
""" Avancer d’une case dans la direction. """
# mettre à jour self.x
if self.direction == 1: # est
self.x += 1
elif self.direction == 3: # ouest
self.x -= 1
# mettre à jour self.y
if self.direction == 0: # nord
self.y += 1
elif self.direction == 2: # sud
self.y -= 1
def pivoter(self):
""" Pivoter ce robot de 90° vers la droite. """
self.direction = (self.direction + 1) % 4
Et le résultat :
r1 = (4, 10)>est
r2 = (15, 7)>sud
r1 = (4, 10)>sud
r2 = (15, 6)>sud
r2 = (15, 6)>ouest
Robot.pivoter : <function Robot.pivoter at 0x7f0b128429d8>
r2.pivoter : <bound method Robot.pivoter of <robot.Robot object at 0x7f0b12847748>>
class Robot:
""" ... """
Exercices
Exercice 2 : Date
Proposer une modélisation UML d’une classe Date.
Exercice 3 : Monnaie
1 Modéliser en UML une classe Monnaie. Une monnaie est caractérisée par une valeur (int) et
une devise (str) et possède les opérations ajouter et retrancher pour respectivement
ajouter et retrancher à cette monnaie une autre monnaie. Les deux monnaies doivent avoir
même devise sinon l’exception TypeError est levée.
2 Écrire deux tests. Le premier ajoute à la monnaie m1 de 5 euros la monnaie m2 de 7 euros
et vérifie que m1 vaut bien 12 euros. Le deuxième retranche à m1 m2 et vérifie que la
valeur de m1 est -2.
3 Écrire en Python la classe Monnaie.
Exercice 4 : Fraction
Proposer une modélisation UML d’une classe Fraction.
Sommaire
Classe
Les méthodes et attributs définis sur la classe comme « unité d’encapsulation » pourront être
appliqués à ses objets.
Objet
Objet : donnée en mémoire qui est le représentant d’une classe.
l’objet est dit instance de la classe
Un objet est caractérisé par :
un état : la valeur des attributs (x, y, etc.) ;
un comportement : les méthodes qui peuvent lui être appliquées (avaner, etc.) ;
une identité : identifie de manière unique un objet (p.ex. son adresse en mémoire).
Un objet est créé à partir d’une classe (en précisant les paramètres effectifs de son
constructeur, sauf le premier, self) :
Robot(4, 10, ’Est’) # création d’un objet Robot
Robot(direction=’Sud’, x=15, y=7) # c’est comme pour les sous-programmes
Les identités des objets sont conservées dans des variables (des noms) :
1 class A:
2 ac = 10 # Attribut de classe
3 def __init__(self, v):
4 self.ai = v # ai attribut d’instance
5 a = A(5) #------------------------------
6 assert a.ai == 5
7 assert A.ac == 10
8 assert a.ac == 10 # on a accès à un attribut de classe depuis un objet
9 # A.ai # AttributeError: type object ’A’ has no attribute ’ai’
10 b = A(7) #------------------------------
11 assert b.ai == 7
12 assert b.ac == 10
13 b.ai = 11 #------------------------------
14 assert b.ai == 11 # normal !
15 assert a.ai == 5 # ai est bien un attribut d’instance
16 A.ac = 20 #------------------------------
17 assert A.ac == 20 # logique !
18 assert b.ac == 20 # l’attribut de classe est bien partagé par les instances
19 b.ac = 30 #-------------- Est-ce qu’on modifie l’attribut de classe ac ?
20 assert b.ac == 30 # peut-être
21 assert A.ac == 20 # mais non ! b.ac = 30 définit un nouvel attribut d’instance
22
23 assert A is type(a) and A is a.__class__ # obtenir la classe d’un objet
DAFS — Introduction à la programmation objet en Python
Classes et objets
Méthodes
Méthodes
Une méthode d’instance est un sous-programme qui exploite l’état d’un objet (en accès
et/ou en modification).
Le premier paramètre désigne nécessairement l’objet. Par convention, on l’appelle self.
Une méthode de classe est une méthode qui travaille sur la classe (et non l’objet).
Elle est décorée @classmethod.
Son premier paramètre est nommé cls par convention.
@classmethod
def changer_langue(cls, langue):
if langue.lower() == ’fr’:
cls._directions = (’nord’, ’est’, ’sud’, ’ouest’)
else:
cls._directions = (’north’, ’east’, ’south’, ’west’)
Robot.changer_langue(’en’) # Utilisation
Une méthode statique est une méthode définie dans l’espace de nom de la classe mais est
indépendante de cette classe.
Elle est décorée @staticmethod
class Date:
...
@staticmethod
def est_bissextile(annee):
return annee % 4 == 0 and (annee % 100 != 0 or annee % 400 == 0)
__new__(cls, ...) : méthode implicitement statique qui crée l’objet (et appelle
__init__).
__bool__(self) : utilisée quand l’objet est considéré comme booléen et avec la fonction
prédéfinie bool().
...
Voir https://docs.python.org/3/reference/datamodel.html#special-method-names
À éviter !
1 class Date:
2 def __init__(self, jour, mois, annee):
3 self.__jour = jour
4 self.__mois = mois
5 self.__annee = annee
6
7 @property # accès en lecture à mois, comme si c’était un attribut
8 def mois(self):
9 return self.__mois
10
11 @mois.setter # accès en écriture à mois, comme si c’était un attribut
12 def mois(self, mois):
13 if mois < 1 or mois > 12:
14 raise ValueError
15 self.__mois = mois
16
17 if __name__ == "__main__":
18 d1 = Date(25, 4, 2013)
19 assert d1.mois == 4
20 # d1.__mois # AttributeError: ’Date’ object has no attribute ’__mois’
21 assert d1._Date__mois == 4 # à ne pas utiliser !
22 d1.mois = 12
23 assert d1.mois == 12
24 d1.mois = 13 # ValueError
Question : Est-ce que les attributs choisis pour la date sont pertinents ?
DAFS — Introduction à la programmation objet en Python
Classes et objets
Comment définir une classe
Comptes bancaires
Exercice 5 : Compte simple
Nous nous intéressons à un compte simple caractérisé par un solde exprimé en euros, positif ou
négatif, et son titulaire. 1 Il est possible de créditer ce compte ou de le débiter d’un certain
montant.
5.1. Donner le diagramme de classe de la classe CompteSimple.
5.2. Écrire un programme de test de CompteSimple.
5.3. Écrire (puis tester) la classe CompteSimple.
Exercice 6 : Banque
En suivant la même démarche, définir une classe Banque qui gère des comptes. Elle offre les
opérations suivantes :
ouvrir un compte pour un client
calculer le total de l’argent géré par la banque (la somme des soldes de tous les comptes)
prélever des frais sur l’ensemble des comptes
Sommaire
1 Exemple introductif
2 Classes et objets
Relations d’utilisation
3 Relations entre classes
Héritage
Classes abstraites et interfaces
4 Méthodes spéciales
5 Compléments
Préambule
On souhaite faire un éditeur qui permet de dessiner des points, des segments, des polygones,
des cercles, etc.
On considère la classe Point suivante.
1 import math
2 class Point:
Exemple de schéma composé de 3
3 def __init__(self, x=0, y=0):
segments et un point (le barycentre)
4 self.x = float(x)
5 self.y = float(y)
Y
6
7 def __repr__(self):
8 return f"Point({self.x}, {self.y})"
9
10 def __str__(self):
11 return f"({self.x} ; {self.y})"
12
13 def translater(self, dx, dy):
14 self.x += dx
X
15 self.y += dy
16
17 def distance(self, autre):
Documentation omise pour gain de place ! 18 dx2 = (self.x - autre.x) ** 2
19 dy2 = (self.y - autre.y) ** 2
20 return math.sqrt(dx2 + dy2)
Relation d’utilisation
Principe : Une classe utilise une autre classe (en général, ses méthodes).
Exemple : La classe Segment utilise la classe Point. Un segment est caractérisé par ses deux
points extémités :
le translater, c’est translater les deux points extrémités
sa longueur est la distance entre ses extrémités
l’afficher, c’est afficher les deux points extrémités
Remarque : UML définit plusieurs niveaux de relation d’utilisation (dépendance, association,
agrégation et composition) qui caractérisent le couplage entre les classes (de faible à fort).
La classe Segment
1 class Segment:
2 def __init__(self, e1, e2):
3 self.extremite1 = e1
4 self.extremite2 = e2
5
6 def __repr__(self):
7 return f"Segment({repr(self.extremite1)}, {repr(self.extremite2)})"
8
9 def __str__(self):
10 return f"[{self.extremite1} - {self.extremite2}]"
11
12 def translater(self, dx, dy):
13 self.extremite1.translater(dx, dy)
14 self.extremite2.translater(dx, dy)
15
16 def longueur(self):
17 return self.extremite1.distance(self.extremite2)
1 def exemple():
2 # créer les points sommets du triangle
3 p1 = Point(3, 2)
4 p2 = Point(6, 9)
5 p3 = Point(11, 4)
6
7 # créer les trois segments
8 s12 = Segment(p1, p2)
Le résultat obtenu :
9 s23 = Segment(p2, p3)
10 s31 = Segment(p3, p1) [(3.0 ; 2.0) - (6.0 ; 9.0)]
11
[(6.0 ; 9.0) - (11.0 ; 4.0)]
12 # créer le barycentre [(11.0 ; 4.0) - (3.0 ; 2.0)]
13 sx = (p1.x + p2.x + p3.x) / 3.0 (6.666666666666667 ; 5.0)
14 sy = (p1.y + p2.y + p3.y) / 3.0
15 barycentre = Point(sx, sy)
16
17 # construire le schéma
18 schema = [s12, s23, s31, barycentre];
19
20 # afficher le schéma
21 for elt in schema:
22 print(elt)
Exercice
Évolution de l’application
Y
S
Comment faire ?
Idée : faire une nouvelle classe PointNommé, un point avec un nom.
Héritage
Principe : définir une nouvelle classe par spécialisation d’une (ou plusieurs) classes
existantes.
Exemple : Définir une classe PointNommé sachant que la classe Point existe.
La classe PointNommé :
hérite (récupère) tous les éléments de la classe Point
ajoute un nom et les opérations pour manipuler le nom
redéfinit la méthode afficher (pour afficher le nom et les coordonnées du point)
Point est la super-classe, PointNommé la sous-classe
Redéfinition : Donner une nouvelle implantation à une méthode déjà présente dans une
super-classe (override, en anglais).
Certains auteurs parlent de surcharge (overload).
Polymorphisme : plusieurs formes pour la même méthode.
plusieurs versions de la méthode afficher dans PointNommé, la sienne et celle de Point
Notation et vocabulaire
Notation UML
classe fille
classe dérivée
PointNommé
sous−classe
Notation en Python
La classe PointNommé
1 class PointNomme(Point): # La classe PointNommé hérite de Point
2 def __init__(self, nom, x=0, y=0):
3 super().__init__(x, y) # initialiser la partie Point du PointNommé
4 self.nom = nom # un nouvel attribut
5
6 def __repr__(self):
7 return f"PointNomme({repr(self.nom)}, {self.x}, {self.y})"
8
9 def __str__(self): # redéfinition
10 return f"{self.nom}:{super().__str__()}"
11 # utilisation de la version de __str__ dans Point
12
13 def nommer(self, nouveau_nom): # une nouvelle méthode
14 self.nom = nouveau_nom
Remarques :
Il y a bien redéfinition de la méthode __str__ de Point dans PointNommé.
Les méthodes de Point sont héritées par PointNommé (ex : translater, distance)
super() est recommandé pour appeler la méthode des super classes. Voir
https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
super() raccourci pour super(PointNomme, self) ici
Ancienne solution : Point.__init__(self, x, y) et Point.__str__(self)
Xavier Crégut (N7) DAFS — Introduction à la programmation objet en Python 38 / 56
DAFS — Introduction à la programmation objet en Python
Relations entre classes
Héritage
Autres aspects
La classe object
C’est l’ancêtre commun à toutes les classes.
Quand on ne précise aucune superclasse, la classe hérite implicitement de object
Héritage multiple
L’ordre des classes à une importance et influt sur le MRO (Method Resolution Order) :
linéarisation des classes parentes pour permettre le call-next-method :
il préserserve l’ordre de gauche à droite précisé dans chaque classe
une sous-classe apparaît avant ses superclasses
une même classe n’apparaît qu’une fois !
il est monotone : l’ajout de nouvelles sous-classes ne modifie par le MRO des superclasses.
Aspect méthododologique
Comment définir une classe par spécialisation ?
1 Est-ce qu’il y a des méthodes de la super-classe à adapter ?
redéfinir les méthodes à adapter !
attention, il y a des règles sur la redéfinition : fonctionner au moins dans les mêmes cas et faire au
moins ce qui est attendu
2 Enrichir la classe : définir de nouveaux attributs et méthodes.
3 Tester la classe
Comme toute classe !
« Si je vois un oiseau qui vole comme un canard, cancane comme un canard, et nage comme un
canard, alors j’appelle cet oiseau un canard » (James Whitcomb Riley)
Exemple : (Inspiré de La programmation orientée objet en Python)
class StrangeFile:
def read(self, size=0):
return ’’
def write(self, s):
print(s[::-1], end=’’)
def close(self):
pass
f = StrangeFile()
print(’foo’, file=f)
affiche ’oof’.
Ça marche... car les méthodes attendues par print sont présentes.
Comment imposer que sur chaque nouvel objet géométrique soient définies les méthodes
translater et afficher ?
La classe ObjetGéométrique
Solution : Définir une nouvelle classe qui généralise Point, Segment, Cercle, etc.
Première idée :
1 class ObjetGeometrique:
2 def translater(self, dx, dy):
3 pass
4 def __str__(self):
5 pass
Meilleure idée :
1 class ObjetGeometrique:
2 def translater(self, dx, dy):
3 raise NotImplementedError
4 def __str__(self):
5 raise NotImplementedError
La bonne solution : dire que les méthodes sont abstraites et donc la classe abstraite !
Utilisation du module abc (Abstract Base Classes), PEP 3119.
1 import abc
2 class ObjetGeometrique(metaclass=abc.ABCMeta):
3 @abc.abstractmethod
4 def translater(self, dx, dy):
5 pass
6 @abc.abstractmethod
7 def __str__(self):
8 pass
Sommaire
1 Exemple introductif
2 Classes et objets
4 Méthodes spéciales
5 Compléments
Méthodes de comparaison
Remarques :
__eq__ : définir l’égalité logique (==) entre objet
(par opposition à l’égalité physique, comparaison des identités, (avec is : id(o1) == id(o2))
doit retourner NotImplemented si l’opération n’est pas implanté pour le couple d’objets.
par défaut, __ne__() utilise __eq()__
functools.total_ordering() : engendrer les autres fonctions à partir d’une seule.
__hash__() : donne un hash de l’objet. Si deux objets sont égaux, ils doivent avoir le
même hash. Ne pas la définir si les objets sont modifiables !
Exercice 13 Surcharger l’opérateur ’+’ pour additionner deux monnaies et ’*’ pour multiplier
une monnaie par nombre.
Xavier Crégut (N7) DAFS — Introduction à la programmation objet en Python 50 / 56
DAFS — Introduction à la programmation objet en Python
Méthodes spéciales
Émuler un conteneur
class Fib:
__cache = {0: 0, 1: 1}
import timeit
print(’fib(30) :’, timeit.timeit(’fib(30)’, globals=globals(), number=10))
f = Fib()
print(’f(30) :’, timeit.timeit(’f(30)’, globals=globals(), number=10))
print(’f(200) :’, timeit.timeit(’f(200)’, globals=globals(), number=10))
fib(30) : 2.6182310709846206
f(30) : 2.8515001758933067e-05
f(200) : 0.00014783599181100726
Sommaire
1 Exemple introductif
2 Classes et objets
4 Méthodes spéciales
5 Compléments
Références