Auteur : Franck Chambon, Lycée Aubrac
Construisons par exemple une façon de travailler avec les fractions.
# ancienne méthode pour travailler avec des fractions frac1_numérateur = 5 frac1_dénominateur = 7 frac2_numérateur = 13 frac2_dénominateur = 8
Cette façon n'est pas pratique. La gestion des identifiants s'avère pénible dès que les objets sont complexes.
En particulier pour les fonctions et leur paramètres ; comparons :
frac3_numérateur, frac3_dénominateur = addition(frac1_numérateur, frac1_dénominateur, frac2_numérateur, frac2_dénominateur)
frac3 = frac1.addition(frac2)
La seconde est clairement plus lisible, il s'agit de POO.
Le choix d'un nom de classe en Python est d'utiliser le CamelCase, la première lettre de chaque mot en majuscule, et pas de _
.
Pour définir une classe Fraction
:
class Fraction: pass f = Fraction() f.numérateur = 5 f.dénominateur = 8 g = f f.numérateur = 11 print(g.numérateur, "sur", g.dénominateur)
11 sur 8
On constate que l'on peut travailler d'un coup avec tout l'objet. Ici, on n'a pas eu une copie indépendante, mais le même objet pointé par deux variables différentes.
⚠️ C'est mal de donner des attributs à un objet en dehors d'un constructeur. Ne plus jamais refaire.
Les attributs ne sont définis que dans la classe :
class Fraction: numérateur = 5 dénominateur = 8 f = Fraction() h = Fraction() g = f f.numérateur = 11 print("Fraction g :", g.numérateur, "sur", g.dénominateur) print("Fraction h :", h.numérateur, "sur", h.dénominateur)
Fraction g : 11 sur 8 Fraction h : 5 sur 8
⚠️ On aimerait un constructeur pour créer une fraction qui dépend de paramètres. D'autre part, c'est mal de lire ou modifier les attributs directement ; on préfère passer par des fonctions. C'est un aspect de la modularité. On utilisera donc des méthodes pour lire, et d'autres pour modifier.
__init__()
class Fraction: def __init__(self, a, b): self.__numérateur = a self.__dénominateur = b def donne_numérateur(self): return self.__numérateur def donne_dénominateur(self): return self.__dénominateur def modifie_numérateur(self, a): self.__numérateur = a def modifie_dénominateur(self, b): self.__dénominateur = b f = Fraction(22, 5) f.modifie_dénominateur(7) print("Fraction f :", f.donne_numérateur(), "sur", f.donne_dénominateur())
Fraction f : 22 sur 7
Les attributs qui doivent être indiqués privés commencent par __
(double underscore). Ce n'est pas qu'une indication en Python, et ils ne peuvent plus être lus ni modifiés à l'extérieur de la définition de la classe. Dans d'autres langages de programmation, la gestion public/privé est encore plus stricte.
Les méthodes liées au fonctionnement interne sont encadrées de __
; ici __init__()
est la méthode à redéfinir pour initialiser un objet après son constructeur. On ne peut pas changer de nom ! Il existe automatiquement le constructeur __new__
mais en pratique, on réécrit uniquement __init__
.
self
indique l'objet même instancié par la classe. Lui-même (self). On ne peut pas changer de nom !
⚠️ On aimerait une méthode plus simple pour afficher le résultat. On va créer une méthode qui sera appelée par
str
; c'est la méthode__str__()
. On ne peut pas changer de nom !
On ajoute aussi une méthode__repr__()
qui a pour rôle de fournir un affichage bien plus succinct, moins joli, mais pour le débogage. Elle est appelée par la fonctionrepr
. C'est la méthode qui est utilisée en console lorsqu'on entre un objet.
>>> a = 2.0 # un objet quelconque créé >>> print(a) # str sera appelé 2.0 >>> a # repr sera appelé 2.0
Souvent repr
et str
sont identiques, mais ce n'est pas obligatoire. repr
sera plus à destination d'un développeur et str
pour l'utilisateur.
On ajoute dans la classe, juste après le __init__()
# suite de Fraction def __str__(self): return f"Fraction : {f.donne_numérateur()} sur {f.donne_dénominateur()}" def __repr__(self): return f"({f.donne_numérateur()}/{f.donne_dénominateur()})"
Et on teste
>>> f = Fraction(22, 5) >>> f.modifie_dénominateur(7) >>> print(f) Fraction : 22 sur 7 >>> f (22/7)
C'est bien plus pratique à utiliser !
⚠️ Le code n'est pas encore satisfaisant, il manque toutes les docstring.
L'encapsulation est un des trois principes fondamentaux de la POO (avec l'héritage et le polymorphisme).
Il y a donc des méthodes particulières :
On trouve de nombreuses méthodes qui commencent par
get_
ou parset_
, comme :
class Personne: """Classe représentant une personne""" def __init__(self, nom: str, prénom: str, âge: int): self.__nom = nom self.__prénom = prénom self.__âge = âge def get_name(self) -> str: return self.__nom def set_name(self, nom: str): self.__nom = nom
Ajoutons une méthode pour multiplier deux fractions.
# suite de Fraction def multiplie_par(self, fraction): self.__numérateur *= fraction.donne_numérateur() self.__dénominateur *= fraction.donne_dénominateur() >>> f = Fraction(2, 3) >>> g = Fraction(5, 7) >>> f.multiplie_par(g) >>> f (10/21)
⚠️ On remarquera, que pour self
, on peut travailler avec ses attributs privés, mais pour fraction
, nous avons utilisé les méthodes définies avant.
On reprend le code précédent complet :
class Fraction: def __init__(self, a, b): self.__numérateur = a self.__dénominateur = b def __str__(self): return f"Fraction : {f.donne_numérateur()} sur {f.donne_dénominateur()}" def __repr__(self): return f"({f.donne_numérateur()}/{f.donne_dénominateur()})" def donne_numérateur(self): return self.__numérateur def donne_dénominateur(self): return self.__dénominateur def modifie_numérateur(self, a): self.__numérateur = a def modifie_dénominateur(self, b): self.__dénominateur = b def multiplie_par(self, fraction): self.__numérateur *= fraction.donne_numérateur() self.__dénominateur *= fraction.donne_dénominateur() >>> f = Fraction(2, 3) >>> g = Fraction(5, 7) >>> f.multiplie_par(g) >>> f (10/21)
simplifier()
ajouter(fraction)
Pour la suite, on peut regarder :