Réinventer les classes
Page en construction.
En Python, tout est un dictionnaire
On entend souvent dire que "tout est un dictionnaire" en Python. En effet, pour
les classes et objets, les attributs et méthodes d'une classe sont bel et bien
stockés dans des dictionnaires. Il est possible d'accéder à tous les attributs
d'un objet en utilisant l'attribut spécial __dict__
de l'objet.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, other):
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
p = Point(0, 0)
print(p.__dict__) # {'x': 0, 'y': 0}
Pour les méthodes, elles sont stockées dans le dictionnaire
__dict__
de la classe. Ce dictionnaire contient toutes les méthodes de la
classe, y compris les méthodes spéciales mais pas méthodes héritées sauf si elles
ont été redéfinies dans la classe fille. Bref ce que le programmeur a défini
dans la classe et les méthodes spéciales par défaut.
Il est possible d'obtenir la classe d'un objet en utilisant l'attribut spécial __class__
.
Ainsi le code suivant affiche le dictionnaire des méthodes de la classe Point
pour l'objet p
.
print(p.__class__.__dict__)
La fonction dir
Il y a beaucoup à dire sur la façon dont Python gère les attributs et les
méthodes des objets et les possibilités pour les manipuler. Nous allons néanmoins
simplement présenter une dernière fonction qui est très utile pour inspecter
les objets : la fonction dir
. Elle combine en une seule liste les attributs et méthodes d'un objet
ou d'une classe et parcours la chaîne d'héritage pour afficher tous les attributs
et méthodes accessibles. Cela évite de devoir utiliser __dict__
et __class__
pour inspecter les objets.
Dans la section précédente, nous avons décris le concept de classe. Dans cette section, nous allons revoir ce même concept, mais en le réinventant.
Un programmeur veut créer des objets 'Personne' et 'Employé'. Dans un langage de programmation qui ne supporte pas les classes, il pourrait utiliser des dictionnaires pour représenter ces objets :
def creer_personne(nom, age):
return {'nom': nom, 'age': age}
p1 = creer_personne('Alice', 25)
p2 = creer_personne('Bob', 30)
print(f"{p1['nom']} a {p1['age']} ans.") # Affiche : Alice a 25 ans.
print(f"{p2['nom']} a {p2['age']} ans.") # Affiche : Bob a 30 ans.
On pourrait aussi ajouter des fonctions pour manipuler ces objets :
def dire_bonjour(personne):
print(f"Bonjour, je m'appelle {personne['nom']}.")
Ainsi, la clé du dictionnaire sert à accéder à l'attribut de l'objet. Les fonctions qui agissent sur ces objets prennent ces dictionnaires en argument. Si on voulait maintenant créer un objet 'Employé', on pourrait ajouter des clés 'salaire' et 'poste' au dictionnaire. On pourrait même réutiliser la fonction 'creer_personne' pour créer un objet 'Employé' :
def creer_employe(nom, age, salaire, poste):
employe = creer_personne(nom, age)
employe['salaire'] = salaire
employe['poste'] = poste
return employe
On voit donc ici apparaître une notion de constructeur et d'héritage. Les classes sont en quelques sortes des dictionnaires améliorés. Elles sont moins générales, car on ne peut pas y ajouter des clés arbitraires, mais cette restriction est justement ce qui les rend plus puissantes. En effet, les attributs d'une classe sont définis explicitement et les méthodes qui agissent sur ces attributs sont aussi définies explicitement. Ceci permet de mieux structurer le code et de le rendre plus lisible. Cela permet aussi à votre éditeur de code de vous aider en vous proposant des suggestions de code et en vous signalant des erreurs.
Sous forme de classe, le code précédent pourrait ressembler à ceci :
class Personne:
def __init__(self, nom, age):
self.nom = nom
self.age = age
def dire_bonjour(self):
print(f"Bonjour, je m'appelle {self.nom}.")
class Employe(Personne):
def __init__(self, nom, age, salaire, poste):
super().__init__(nom, age)
self.salaire = salaire
self.poste = poste
On voit donc que les classes sont une manière plus structurée de gérer des
objets en programmation. Néanmoins les concepts de base sont les mêmes. L'appel
à super().__init__
dans la classe Employe
est l'équivalent de l'appel à
creer_personne
dans la version sans classes. La méthode dire_bonjour
de la
classe Personne
est l'équivalent de la fonction dire_bonjour
de la version
sans classes. Notez que dire_bonjour
de la version sans classes fonctionne
également avec les dictinonnaires créés par creer_employe
de la même manière
que dire_bonjour
de la classe Personne
fonctionne avec les objets de la
classe Employe
.
Pour ce qui est du self
de la méthode __init__
ou dire_bonjour
de la
classe Personne
, il remplace le dictionnaire passé en argument dans la version
sans classes. En effet, self
est une référence à l'instance de la classe
elle-même. Ainsi, self.nom
est l'équivalent de personne['nom']
dans la
version sans classes. De plus, self
est passé automatiquement en argument à
toutes les méthodes de la classe, ce qui simplifie le code.