T2.1 POO (part two)⚓︎
2.1.4 Des inconvénients⚓︎
Mauvaise utilisation⚓︎
Reprenons l'exemple de la classe Voiture
de l'exercice 2 et imaginons l'utilisation suivante:
>>> dmc12 = Voiture(0, 20)
>>> dmc12.remplir(25000)
>>> dmc12.avance(-500)
>>> dmc12.affiche()
La voiture a parcouru -500 kilomètres et il y a 25100.0 litres d'essence dans le réservoir.
Quels problèmes illustre cet exemple?
Différentes implémentations⚓︎
Revenons maintenant sur la classe Chrono
de l'exercice 3. L'objectif de cette classe est de manipuler un chronomètre, et donc d'utiliser exclusivement les méthodes affiche
et avance
. On aurait donc très bien pu ne gérer qu'un seul attribut temps
donnant le temps en secondes, et de calculer les heures et minutes à partir de la valeur de cet attribut. Par exemple:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
2.1.5 Un remède: l'encapsulation⚓︎
Privé ≠ Public
Dans la philosophie de la POO, les attributs doivent être privés, c'est à dire qu'ils ne doivent pas être modifiables directement. Leur manipulation doit se faire uniquement par des méthodes. L'utilisateur ne doit pas avoir besoin de connaître ces attributs, cela reste dans les choix d'implémentation.
Les méthodes, elles, sont publiques: elles constituent ce qu'on appelle l'interface de la classe.
Pour accéder ou modifier les valeurs des attributs, on passe donc par des méthodes dédiées : c'est le principe de l'encapsulation.
Exemple: accesseurs et mutateurs
1 2 3 4 5 6 7 8 9 10 11 12 |
|
▶ La méthode get_kilometrage
est ce qu'on appelle un accesseur (getter en anglais). Sa seule et unique vocation est de donner la valeur de l'attribut correspondant, celui-ci n'étant pas accessible puisque privé.
▶ La méthode set_kilometrage
est ce qu'on appelle un mutateur (setter en anglais). Il permet de modifier la valeur de l'attribut, en permettant d'effectuer tous les contrôles éventuels sur cette valeur.
Exercice 6
- Écrire un accesseur et un mutateur pour l'attribut
carburant
. - Réécrire la méthode
avance
de la classeVoiture
en utilisant les accesseurs et mutateurs.
Exercice 7
Reprendre la classe Chrono
(implémentation ci-dessus, avec un seul attribut) en ajoutant les méthodes getter et setter (avec contrôle sur l'argument) et en modifiant les méthodes existantes.
2.1.6 Compléments (Hors programme)⚓︎
Méthodes spéciales⚓︎
Les méthodes spéciales (parfois appelées méthodes magiques) sont encadrées par des __
. Ces méthodes (il en existe environ une centaine) sont
appelées dans des contextes particuliers (par exemple __init__
est appelée après que l’objet a été alloué,
pour initialiser ses attributs). Utiliser __init__
est un passage obligé.
Un autre passage presque obligé est l’obtention d’un affichage human-friendly d’un objet, ce qu'on a fait avec nos fonctions affiche
dans les exemples/exercices précédents.
-
__str__
: donne une représentation de l'objet en chaîne de caractères dès que Python en a besoin, par exemple pourprint
. -
__repr__
: est utilisée pour afficher l'objet lors de son évaluation, en console par exemple. S’il n’y a pas de méthode__str__
c’est__repr__
qui est utilisée lors d’un affichage avecprint
. -
__len__
est appelée automatiquement si on demande la taille d’un objet avec la fonctionlen
.
Exemples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
>>> c = Chrono(1978)
>>> c
<__main__.Chrono object at 0x7fc8986c7cd0>
>>> print(c)
<__main__.Chrono object at 0x7fc8986c7cd0>
>>> c.affiche()
'Il est 0 heures, 32 minutes et 58 secondes.'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
>>> c = Chrono(1978)
>>> c
<__main__.Chrono object at 0x7f701d943460>
>>> print(c)
Il est 0 heures, 32 minutes et 58 secondes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
>>> c = Chrono(1978)
>>> c
Il est 0 heures, 32 minutes et 58 secondes.
>>> print(c)
Il est 0 heures, 32 minutes et 58 secondes.
Avec cette méthode spéciale, on décide de ce que signifie la taille (longueur) de l'objet (si cela signifie quelque chose, n'est-ce pas... ). Si elle n'est pas définie dans la classe, appeler la fonction len
entraînera une erreur:
TypeError: object of type 'Chrono' has no len()
1 2 3 4 5 6 7 8 |
|
>>> mpi = Parcours(['Maths', 'NSI'], ['Maths expertes'])
>>> len(mpi)
9
Propriétés⚓︎
En POO, Python permet de combiner:
- le respect des getters et setters (problème général à la POO);
- la souplesse syntaxique de la manipulation des attributs.
On utilise pour cela des propriétés. Pour l'illustrer, on observe une classe Point
qui représente un point par une abscisse et une ordonnée (comme c'est original) positives
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
_x
est un attribut, get_x
et set_x
sont des méthodes (qui se trouvent être un getter et un setter), et x
est une propriété.
Si on écrit à présent :
>>> p = Point(1, 0)
>>> p.x
1
>>> p.x = -2
>>> p.x
1
>>> p.x += 3
>>> p.x
4
En utilisant la propriété x
, Python utilise automatiquement le getter ou le setter selon le contexte. Noter que dans p.x += 3
, le getter et le setter sont utilisés.
Décorateurs⚓︎
Voici une autre syntaxe possible, mais on n'a plus accès directement aux getter et setter.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Héritage⚓︎
Un des piliers de la POO (mais hors-programme) est le concept d'héritage. En bref, cela signifie qu'une classe peut être écrite à partir d'une classe parent déjà existante, et donc héritera de ses attributs et méthodes.
Par exemple, si on veut des points colorés, il suffit d'ajouter un atttibut de couleur à la classe précédente.
1 2 3 4 |
|
On signale que la classe PointColore
hérite de la classe parent Point
(ligne 1).
Ensuite, la méthode __init__
a été redéfinie. On notera l’appel à la méthode __init__
de la classe parent en utilisant super
pour les attributs de la classe Point
.
Toute instance de la classe PointColore
bénéficie des méthodes dejà existantes dans Point
, sans avoir besoin de les redéfinir.