10 Pour quelques notions de plus
Cette partie présente quelques outils que propose Python pour une meilleur implémentation de l’Objet.
10.1 Le paramètre self de la déclaration des méthodes dans la classe
Lorsque vous écrivez une méthode, vous êtes tenus de déclarer un premier paramètre qui par convention, aura pour nom self. Ce paramètre est destiné à recevoir l’instance. Explication…
Les objets sont des instances de classes. Le principe de la programmation orientée objet consiste à définir des entités qui regroupent un état et des comportements. Il paraît évident de se représenter les objets comme des composants construits sur le modèle d’une classe et qui embarquent données (les attributs) et comportements. Mais en Python, l’implémentation de la POO est par prototype, ce n’est donc pas tout à fait ça.
Commençons par définir une classe et créons une instance.
In [1]: class Demo:
...: def __init__(self, name):
...: self.name = name
...:
...: def say_hello(self):
...: print(f"Hello, I'm {self.name}")
...:
In [2]: d = Demo("One")Puisque les méthodes sont des fonctions, si nous faisons appel à d.say_hello, nous devons avoir en retour une référence à la méthode (fonction) say_hello de l’objet d.
In [3]: d.say_hello
Out[3]: <bound method Demo.say_hello of <__main__.Demo object at 0x102d2ceb0>>D’après ce retour, nous pouvons vérifier que nous pouvons accéder à Demo.say_hello.
In [4]: Demo.say_hello
Out[4]: <function __main__.Demo.say_hello(self)>Python voit donc say_hello(self) comme une fonction définie dans la classe Demo, perçue comme un namespace.
La méthode say_hello() de l’objet (instance) d est donc une méthode liée à cette fonction.
Nous pouvons donc exécuter la fonction Demo.say_hello()… Ce qui va lever une exception car il manque un arguments. Cet argument correspond au paramètre self.
Le paramètre self attends en effet une instance de Demo.
In [5]: Demo.say_hello(d)
Hello, I'm One Cette instruction, c’est ce que l’interpréteur exécute réellement. Nous écrivons une instruction qui fait appel à la méthode de l’instance et l’interpréteur va exécuter la méthode de la classe en fournissant en premier paramètre l’instance. Ce paramètre self est donc un paramètre de la méthode de la classe destiné à recevoir l’instance sur laquelle exécuter la méthode.
10.2 Notion de properties
Lorsqu’un attribut est déclaré comme non_public, pour accéder à sa valeur, li
Cette partie ne présente les properties que en tant que décorateurs
10.2.1 Accesseurs (getters)
class MyClass:
@property
def my_property(self):
return 42Usage:
>>> my_object = MyClass()
>>> my_object.my_property
4210.2.2 Mutateurs (setters)
Le mutateur nécessite qu’un accesseur ai été défini
class MyClass:
def __init__(self):
self._property_value = 42
@property
def my_property(self):
return self._property_value
@my_property.setter
def my_property(self, new_value):
self._property_value = new_valueUsage:
>>> my_object = MyClass()
>>> my_object.my_property
42
>>> my_object.my_property = 24
>>> my_object.my_property
2410.2.3 Cas pratique : notre formation
Si je reprends notre formation en ne m’intéressant qu’aux attributs, sa conception logique doit être :
Note : l’outil “mermaid” utilisé pour générer ce schéma ne supporte pas la représentation standard qui devrait être -List<String> students {readonly}.
class Training:
def __init__(self, subject:str, duration:int, seats:int):
self.subject = subject
self._duration = duration
self._seats = seats
self.students = []Pour les attributs en lecture seule duration et students, l’ajout de properties accesseurs seules est évidente. Attention à ne pas retourner la référence vers _students.
@property
def duration(self):
return self._duration
@property
def students(self):
return self._students.copy()Le diagramme UML indique que nous devons avoir un attribut max_seats en lecture seule représentant le nombre maximum de places. Sauf que nous avons un attribut non_public _seats et non _max_seats. Et ceci n’est pas un problème : rien n’oblige à ce que les attributs exposés (publiques) référencent un attribut privé du même nom. Donc, on ajoute la property
@property
def max_seats(self):
return self._seatsEnfin, il y a un attribut en lecture seule available_seats. Celui-ci représente la différence entre le nombre de places de la formation et le nombre d’inscrits. Au lieu de retourner la valeur d’un attribut privé, nous pouvons calculer cette valeur :
@property
def available_seats(self):
return self.seats - len(self._studentsEt ceci illustre en quoi les properties sont un outil fondamental de l’encapsulation : elles permettent de décorréler l’implémentation de l’interface de l’objet. De l’extérieur, on voit un état cohérent pour l’utilisateur de l’objet mais l’implémentation utilise un état cohérent pour son traitement.