56 votes

Python : comprendre les variables de classe et d'instance

Je pense que j'ai une idée fausse des 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']

Je pensais que energy y skill étaient des variables de classe, car je les ai déclarées en dehors 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 incorrecte ?). Mais les résultats me montrent que energy prend des valeurs différentes pour chaque objet (comme une variable d'instance), alors que skills semble être partagée (comme une variable de classe). Je pense que j'ai raté quelque chose d'important...

4 votes

Cette question est plus subtile que ce doublon, puisqu'elle porte sur la différence de comportement entre deux attributs de classe. Je suis sûr qu'un doublon existe encore, mais pas celui-là.

4 votes

Oui, tu l'as fait. L'énergie est immuable et l'assigner à celle-ci remplace la variable, mais sur l'instance, laissant la classe seule. D'un autre côté, vous ne remplacez pas les compétences, vous ajoutez à l'instance partagée sur la classe.

2 votes

Quant à la réponse, vous n'avez pas "modifié les valeurs de la même manière" comme vous le prétendez. Vous avez modifié l'énergie avec self.energy -= 1 , un devoir ; vous avez modifié skills avec self.skills.append(...) un appel de méthode. Ce sont des choses différentes.

38voto

Daniel Roseman Points 199743

L'astuce ici est de comprendre ce que self.energy -= 1 fait. Il s'agit en fait de deux expressions, l'une récupérant la valeur de self.energy - 1 et l'autre l'attribue à self.energy .

Mais ce qui vous trouble, c'est que les références ne sont pas interprétées de la même façon des deux côtés de cette mission. Quand on demande à Python de récupérer self.energy il essaie de trouver cet attribut sur l'instance, échoue et se rabat sur l'attribut de classe. Cependant, lorsqu'il attribue à self.energy il sera toujours attribué à un attribut d'instance, même s'il n'existait pas auparavant.

0 votes

Il s'agit également d'une astuce intéressante pour remplacer facilement les valeurs par défaut. Vous avez une bonne valeur par défaut sur la classe, mais l'assigner sur une instance l'écrase juste pour cette instance.

0 votes

Il faudrait probablement mentionner le new_skill() fantômes entre les classes dans cet exemple.

0 votes

Pouvez-vous suggérer la manière correcte d'écrire le new_skill méthode ? Je fais de mon mieux pour aborder la famille C avec un esprit ouvert, mais des choses comme ça me font me demander comment python est devenu si populaire. (Non pas que le C et ses dérivés soient sans défauts, mais il s'agit là de trucs assez basiques de classe/encapsulation).

33voto

B. M. Points 5323

Vous rencontrez des problèmes d'initialisation basés sur la mutabilité.

Premier la solution. skills y energy sont des attributs de classe. C'est une bonne pratique de les considérer comme étant en lecture seule, en tant que valeurs initiales pour les attributs 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=None):
        self.energy = en
        self.skills = [] if sk is None else sk 

   ....

Alors chaque instance aura ses propres attributs, tous vos problèmes disparaîtront.

Deuxièmement qu'est-ce qui se passe avec ce code ? Pourquoi est-ce que skills partagé, lorsque energy est par instance ?

El -= l'opérateur est subtil. il s'agit de en place assignation si possible. La différence ici est que list sont mutables, de sorte qu'une modification in situ se produit souvent :

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

[] 201781512
['strong'] 201781512

Alors a1.skills y a2.skills sont la même liste, qui est également accessible en tant que Animal.skills . Mais energy est un élément non-mutable int La modification est donc impossible. Dans ce cas, une nouvelle int est créé, de sorte que chaque instance gère sa propre copie de l'objet energy variable :

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

10 1360251232
9 1360251200

24voto

ChrisFreeman Points 774

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

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

Quand vous attribuer à un 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 attribute is made local to instance
>>> id(a2.energy)
31346816

El new_skill() La méthode ne attribuer une nouvelle valeur à la skills mais plutôt appends qui modifie la liste en place.

Si vous deviez ajouter manuellement une compétence, alors l'option skills La liste 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 supprimez l'attribut d'instance a1.skills la référence reviendrait à l'attribut de classe :

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

6voto

Accédez aux variables de la classe à travers la classe, et non à travers self :

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)

0 votes

Pourquoi ne pas utiliser le nom de la classe pour plus de lisibilité ? Animal.skills.append(skill)

3voto

En fait, dans votre code a1.travail() ; imprimer a1.énergie ; imprimer a2.énergie

Lorsque vous appelez a1.work(), une variable d'instance pour l'objet a1 est créée avec le même nom, à savoir 'energy'. Et quand l'interpréteur arrive à 'print a1.energy', il exécute la variable d'instance de l'objet a1. Et quand l'interpréteur arrive à 'print a2.energy' il exécute la variable de classe, et puisque vous n'avez pas changé la valeur de la variable de classe il montre 10 comme sortie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X