Réinventer les classes
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.
Réinventer les classes
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.