Python: comprendre les variables de classe et d’instance

Je pense que j’ai une idée fausse concernant les variables de classe et d’instance. Voici un exemple de code:

class Animal(object): energy = 10 skills = [] def work(self): print 'I do something' self.energy -= 1 def new_skill(self, skill): self.skills.append(skill) if __name__ == '__main__': a1 = Animal() a2 = Animal() a1.work() print a1.energy # result:9 print a2.energy # result:10 a1.new_skill('bark') a2.new_skill('sleep') print a1.skills # result:['bark', 'sleep'] print a2.skills # result:['bark', 'sleep'] 

J’ai pensé que l’ energy et la skill étaient des variables de classe, car je les ai déclarées hors de toute méthode. Je modifie ses valeurs à l’intérieur des méthodes de la même manière (avec self dans sa déclaration, peut-être incorrect?). Mais les résultats me montrent que l’ energy prend différentes valeurs pour chaque object (comme une variable d’instance), alors que les skills semblent être partagées (comme une variable de classe). Je pense que j’ai raté quelque chose d’important …

Vous rencontrez des problèmes d’initialisation liés à la mutabilité.

Tout d’abord , le correctif. skills et l’ energy sont des atsortingbuts de classe. Il est conseillé de les considérer en lecture seule, en tant que valeurs initiales pour les atsortingbuts d’instance. La manière classique de construire votre classe est la suivante:

 class Animal(object): energy = 10 skills = [] def __init__(self,en=energy,sk=skills): self.energy=en self.skills=sk .... 

Chaque instance aura alors ses propres atsortingbuts, tous vos problèmes disparaîtront.

Deuxièmement , que se passe-t-il avec ce code? Pourquoi les skills partagées lorsque l’ energy est par instance?

L’opérateur -= est subtil. c’est pour l’atsortingbution sur place si possible. La différence ici est que les types de list sont mutables, de sorte qu’une modification sur place se produit souvent:

 In [6]: b=[] print(b,id(b)) b+=['strong'] print(b,id(b)) [] 201781512 ['strong'] 201781512 

Donc, a1.skills et a2.skills sont la même liste, qui est également accessible en tant que Animal.skills . Mais l’ energy est un int non mutable, donc la modification est impossible. Dans ce cas, un nouvel object int est créé, donc chaque instance gère sa propre copie de la variable energy :

 In [7]: a=10 print(a,id(a)) a-=1 print(a,id(a)) 10 1360251232 9 1360251200 

L’astuce consiste ici à comprendre ce que self.energy -= 1 fait. C’est vraiment deux expressions; celui qui obtient la valeur de self.energy - 1 , et celui qui le redonne à l’ self.energy .

Mais ce qui vous embrouille, c’est que les références ne sont pas interprétées de la même manière des deux côtés de la mission. Lorsqu’on self.energy à Python d’obtenir self.energy , il essaie de trouver cet atsortingbut sur l’instance, échoue et revient à l’atsortingbut de classe. Cependant, quand il assigne à self.energy , il va toujours affecter un atsortingbut d’instance, même si cela n’existait pas auparavant.

Lors de la création initiale, les deux atsortingbuts sont le même object:

 >>> a1 = Animal() >>> a2 = Animal() >>> a1.energy is a2.energy True >>> a1.skills is a2.skills True >>> a1 is a2 False 

Lorsque vous affectez un atsortingbut de class , il est rendu local à l’instance:

 >>> id(a1.energy) 31346816 >>> id(a2.energy) 31346816 >>> a1.work() I do something >>> id(a1.energy) 31346840 # id changes as atsortingbute is made local to instance >>> id(a2.energy) 31346816 

La méthode new_skill() n’atsortingbue pas de nouvelle valeur au tableau de skills , mais appends qui modifie la liste en place.

Si vous deviez append manuellement une compétence, la liste de skills serait locale à l’instance:

 >>> id(a1.skills) 140668681481032 >>> a1.skills = ['sit', 'jump'] >>> id(a1.skills) 140668681617704 >>> id(a2.skills) 140668681481032 >>> a1.skills ['sit', 'jump'] >>> a2.skills ['bark', 'sleep'] 

Enfin, si vous supprimiez l’atsortingbut d’instance a1.skills , la référence reviendrait à l’atsortingbut de classe:

 >>> a1.skills ['sit', 'jump'] >>> del a1.skills >>> a1.skills ['bark', 'sleep'] >>> id(a1.skills) 140668681481032 

Accédez aux variables de classe via la classe, et non par soi-même:

 class Animal(object): energy = 10 skills = [] def work(self): print 'I do something' self.__class__.energy -= 1 def new_skill(self, skill): self.__class__.skills.append(skill) 

En fait, dans votre code a1.work (); imprimer a1.energy; imprimer a2.energy

Lorsque vous appelez a1.work (), une variable d’instance pour un object a1 est créée avec le même nom que «énergie». Et lorsque l’interpréteur arrive à ‘imprimer a1.energy’, il exécute la variable d’instance de l’object a1. Et lorsque l’interpréteur arrive à «imprimer a2.energy», il exécute la variable de classe et, comme vous n’avez pas modifié la valeur de la variable de classe, il affiche 10 en sortie.