Définition
Un programme manipule rarement un seul compte ou un seul utilisateur. Il en gère des dizaines, semblables par leur forme et différents par leurs valeurs. Une variable par compte devient vite intenable : à cinquante clients, personne ne s'y retrouve.
D'où la séparation entre la classe et ses instances. La classe décrit ce qu'un objet contient et sait faire, mais elle ne détient rien : c'est un plan. L'instance est l'exemplaire fabriqué à partir de ce plan, qui vit en mémoire avec ses propres valeurs. Une classe Compte ne détient l'argent de personne : c'est chaque compte ouvert qui porte un solde.
L'exemple suivant crée deux comptes à partir de cette classe, puis crédite le premier. Regardez ce qu'affiche le second.
class Compte:
def __init__(self, titulaire):
self.titulaire = titulaire # une valeur propre à cet objet
self.solde = 0
premier = Compte("Camille") # une instance
second = Compte("Sofia") # une autre, indépendante
premier.solde = 120
print(second.solde) # 0 : le second n'a rien vu passerLes deux comptes sortent du même plan et ne partagent pourtant rien. Créditer le premier laisse le second intact : voilà ce qui définit une instance, des valeurs bien à elle. Mille comptes peuvent coexister sans jamais se marcher dessus.
Ce qui appartient à l'instance, ce qui appartient à la classe
Reste à savoir où poser une valeur, car Python offre deux emplacements très ressemblants à la lecture. Un attribut écrit dans le corps de la classe est créé une seule fois, au chargement du fichier, et tous les objets s'y réfèrent. Un attribut posé sur self est recréé à chaque construction : il en existe autant que d'objets.
Le tableau met les deux écritures côte à côte, la dernière colonne d'abord : ce qui les sépare tient à l'usage, pas à la syntaxe.
| Écriture | Où vit la valeur | Conséquence |
|---|---|---|
frais = 2 dans la classe | Sur la classe | Une seule valeur pour tout le programme |
self.solde = 0 dans __init__ | Sur l'instance | Une valeur par objet créé |
historique = [] dans la classe | Sur la classe | La même liste pour tous les objets |
La troisième ligne est le piège, invisible à la relecture. Une collection mutable déclarée au niveau de la classe est construite une seule fois, au chargement du fichier : chaque objet qui y ajoute une opération écrit chez tous les autres. Sans la moindre erreur, l'historique du premier client finit par contenir les mouvements du dernier.
Les deux lignes ci-dessous se confondent facilement, alors qu'une seule donne à chaque objet son propre historique.
class Compte:
historique = [] # partagé par tous : presque toujours une erreur
def __init__(self):
self.historique = [] # recréé pour chaque instance : ce qu'on veutUne liste ou un dictionnaire écrit au niveau de la classe existe en un seul exemplaire pour tout le programme, et la panne remonte bien plus tard, souvent en production. Une valeur qui change d'un objet à l'autre s'écrit sur self, dans __init__.
Ce que Python fait quand la classe est appelée
Écrire Compte("Camille") ressemble à un appel de fonction, et l'on croit volontiers que cela exécute la seule méthode d'initialisation. Il s'en passe un peu plus : __new__ fabrique l'objet vide, puis __init__ le remplit. La première étape crée l'instance, la seconde lui donne son contenu. La nuance ne se voit que sur les cas particuliers, comme un objet immuable, à préparer avant sa création.
Vient alors une autre question : comment une méthode sait-elle sur quel objet elle travaille ? Dans chaque méthode, le premier paramètre reçoit l'instance sur laquelle l'appel a été fait, et Python le passe automatiquement. C'est tout le rôle de self, simple nom de paramètre rendu universel par la convention. Appeler premier.crediter(50) revient à écrire Compte.crediter(premier, 50), forme qui rend visible ce que la première dissimule.
Vérifier qu'un objet est bien l'instance attendue
Aux frontières d'un programme arrivent des objets dont on ignore la provenance, et il faut parfois vérifier leur nature. La fonction isinstance répond à cette question et accepte les descendants : un objet issu d'une classe fille passe le test de la classe mère, là où une comparaison sur le type le refuse.
La différence compte dès qu'un héritage entre en jeu, sous peine de rejeter des objets valides. L'exemple pose les deux contrôles sur un livret, sorte de compte.
class Livret(Compte):
pass
epargne = Livret("Camille")
isinstance(epargne, Compte) # True : un livret est bien un compte
type(epargne) is Compte # False : son type exact reste LivretLes deux réponses sont justes : isinstance demande si l'objet sait se comporter comme un compte, type de quelle classe exacte il sort. En pratique, la première question suffit presque toujours.
Cela dit, la culture Python n'encourage pas ces contrôles. Le duck typing préfère appeler la méthode attendue et rattraper l'AttributeError si elle manque, ce qui laisse la porte ouverte à des objets d'une classe imprévue. Le test d'instance garde sa place là où arrivent des données dont la forme n'est garantie par personne.
Questions fréquentes
Quelle est la différence entre une classe et une instance ?
La classe est le plan, l'instance est l'objet construit à partir de ce plan. Le plan ne bouge plus une fois écrit, alors que les instances se créent à volonté pendant l'exécution, chacune avec ses valeurs et sa place en mémoire. Une valeur qui diffère d'un objet à l'autre appartient donc à l'instance.
Combien d'instances peut-on créer à partir d'une seule classe ?
Autant que la mémoire le permet, rien ne les limite par défaut. Une limite, s'il en faut une, se pose à la main : une classmethod sert justement à compter ces créations ou à les encadrer, par exemple pour n'autoriser qu'un seul exemplaire.
Pourquoi l'affichage d'une instance ressemble-t-il à une adresse mémoire ?
Parce que Python, faute d'instructions, se contente du nom de la classe et de l'emplacement de l'objet : il ignore lesquelles de vos valeurs méritent d'être montrées. Définir la méthode magique __repr__ remplace cette ligne par ce que vous jugez utile, le titulaire et le solde par exemple. La formation Python construit ces classes pas à pas.