152 votes

Quelle est la différence entre les attributs de classe et les attributs d'instance ?

Y a-t-il une distinction significative entre:

classe A(objet):
    foo = 5   # une valeur par défaut

vs.

classe B(objet):
    def __init__(self, foo=5):
        self.foo = foo

Si vous créez beaucoup d'instances, y a-t-il une différence de performances ou d'exigences d'espace pour les deux styles? Lorsque vous lisez le code, considérez-vous que la signification des deux styles est significativement différente?

1 votes

Je viens de réaliser qu'une question similaire a été posée et répondue ici: stackoverflow.com/questions/206734/… Dois-je supprimer cette question?

3 votes

C'est votre question, n'hésitez pas à la supprimer. Puisque c'est la vôtre, pourquoi demander l'opinion de quelqu'un d'autre?

163voto

Alex Coventry Points 11090

Il existe une différence sémantique significative (au-delà des considérations de performance) :

  • lorsque l'attribut est défini sur l'instance (ce que nous faisons généralement), il peut y avoir plusieurs objets auxquels il est fait référence. Chacun obtient une version totalement séparée de cet attribut.
  • lorsque l'attribut est défini sur la classe, il n'y a qu'un seul objet sous-jacent auquel il est fait référence. Ainsi, si des opérations sur différentes instances de cette classe tentent toutes de définir/(ajouter/étendre/insérer/etc.) l'attribut, alors :
    • si l'attribut est un type intégré (comme int, float, booléen, chaîne de caractères), les opérations sur un objet écraseront la valeur
    • si l'attribut est un type mutable (comme une liste ou un dictionnaire), nous obtiendrons des fuites non désirées.

Par exemple :

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]

4 votes

Seuls les types mutables sont partagés. Comme pour int et str, ils sont toujours attachés à chaque instance plutôt qu'à la classe.

14 votes

@Babu: Non, int et str sont également partagés, de la même manière. Vous pouvez facilement vérifier cela avec is ou id. Ou il suffit de regarder dans le __dict__ de chaque instance et dans le __dict__ de la classe. En général, cela n'a pas vraiment d'importance que les types immuables soient partagés ou non.

20 votes

Cependant, notez que si vous faites a.foo = 5, alors dans les deux cas vous verrez b.foo retourner []. Cela est dû au fait que dans le premier cas, vous écrasez l'attribut de classe a.foo avec un nouvel attribut d'instance portant le même nom.

44voto

Peter Shinners Points 1865

La différence est que l'attribut sur la classe est partagé par toutes les instances. L'attribut sur une instance est unique à cette instance.

Si vous venez de C++, les attributs sur la classe sont plus similaires à des variables membres statiques.

1 votes

N'est-ce pas seulement les types mutables qui sont partagés ? La réponse acceptée montre une liste, ce qui fonctionne, mais s'il s'agit d'un int, il semble être le même qu'un attribut d'instance : >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5

8 votes

@Rafe: Non, tous les types sont partagés. La raison pour laquelle vous êtes confus est que ce que a.foo.append(5) mute la valeur à laquelle a.foo se réfère, tandis que a.foo = 5 fait de a.foo un nouveau nom pour la valeur 5. Ainsi, vous vous retrouvez avec un attribut d'instance qui masque l'attribut de classe. Essayez la même chose a.foo = 5 dans la version d'Alex et vous verrez que b.foo reste inchangé.

40voto

zangw Points 401

Voici un très bon post, et le résumé est le suivant.

class Bar(object):
    ## Pas besoin de syntaxe point
    class_var = 1

    def __init__(self, i_var):
        self.i_var = i_var

## Besoin de la syntaxe point car nous avons quitté l'espace de nom de la classe
Bar.class_var
## 1
foo = Bar(2)

## Trouve i_var dans l'espace de nom d'instance de foo
foo.i_var
## 2

## Ne trouve pas class_var dans l'espace de nom d'instance…
## Donc regarde dans l'espace de nom de la classe (Bar.__dict__)
foo.class_var
## 1

Et sous forme visuelle

enter image description here

Attribution d'attribut de classe

  • Si un attribut de classe est défini en accédant à la classe, il remplacera la valeur pour toutes les instances

      foo = Bar(2)
      foo.class_var
      ## 1
      Bar.class_var = 2
      foo.class_var
      ## 2
  • Si une variable de classe est définie en accédant à une instance, elle remplacera la valeur uniquement pour cette instance. Cela remplace essentiellement la variable de classe et la transforme en une variable d'instance disponible, de manière intuitive, uniquement pour cette instance.

      foo = Bar(2)
      foo.class_var
      ## 1
      foo.class_var = 2
      foo.class_var
      ## 2
      Bar.class_var
      ## 1

Quand utiliser un attribut de classe?

  • Stockage de constantes. Comme les attributs de classe peuvent être accédés en tant qu'attributs de la classe elle-même, il est souvent utile de les utiliser pour stocker des constantes spécifiques à la classe

      class Circle(object):
           pi = 3.14159
    
           def __init__(self, radius):
                self.radius = radius   
          def area(self):
               return Circle.pi * self.radius * self.radius
    
      Circle.pi
      ## 3.14159
      c = Circle(10)
      c.pi
      ## 3.14159
      c.area()
      ## 314.159
  • Définition de valeurs par défaut. Comme exemple trivial, nous pourrions créer une liste bornée (c'est-à-dire une liste qui ne peut contenir qu'un certain nombre d'éléments ou moins) et choisir d'avoir une limite par défaut de 10 éléments

      class MyClass(object):
          limit = 10
    
          def __init__(self):
              self.data = []
          def item(self, i):
              return self.data[i]
    
          def add(self, e):
              if len(self.data) >= self.limit:
                  raise Exception("Trop d'éléments")
              self.data.append(e)
    
       MyClass.limit
       ## 10

0 votes

Le post que vous avez lié est incroyable!

22voto

abarnert Points 94246

Étant donné que les personnes qui commentent ici et dans deux autres questions marquées comme dupliquées semblent toutes être confuses à ce sujet de la même manière, je pense qu'il vaut la peine d'ajouter une réponse supplémentaire en plus de celle d'Alex Coventry.

Le fait qu'Alex attribue une valeur d'un type mutable, comme une liste, n'a rien à voir avec le fait que les choses sont partagées ou non. Nous pouvons le voir avec la fonction id ou l'opérateur is:

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

(Si vous vous demandez pourquoi j'ai utilisé <code>object()</code> au lieu, disons, de <code>5</code>, c'est pour éviter de rencontrer deux autres problèmes dont je ne veux pas parler ici; pour deux raisons différentes, des <code>5</code> créés entièrement séparément peuvent finir par être la même instance du nombre <code>5</code>. Mais des <code>object()</code> créés entièrement séparément ne le peuvent pas.)


Alors, pourquoi est-ce que a.foo.append(5) dans l'exemple d'Alex affecte b.foo, mais a.foo = 5 dans mon exemple ne le fait pas? Eh bien, essayez a.foo = 5 dans l'exemple d'Alex, et remarquez que cela n'affecte pas non plus b.foo là-bas.

a.foo = 5 fait simplement de a.foo un nom pour 5. Cela n'affecte pas b.foo, ni tout autre nom pour l'ancienne valeur à laquelle a.foo faisait référence.* C'est un peu délicat que nous créons un attribut d'instance qui cache un attribut de classe,** mais une fois que vous avez compris cela, rien de compliqué ne se passe ici.


En espérant que maintenant il est évident pourquoi Alex a utilisé une liste: le fait que vous pouvez muter une liste signifie qu'il est plus facile de montrer que deux variables nomment la même liste, et cela signifie aussi qu'il est plus important dans le code réel de savoir si vous avez deux listes ou deux noms pour la même liste.


* La confusion pour les personnes venant d'un langage comme C++ est que en Python, les valeurs ne sont pas stockées dans des variables. Les valeurs vivent dans le monde des valeurs, par elles-mêmes, les variables ne sont que des noms pour des valeurs, et l'assignation crée simplement un nouveau nom pour une valeur. Si cela aide, pensez à chaque variable Python comme à un <code>shared_ptr<t></t></code> au lieu d'un <code>T</code>.

** Certaines personnes en profitent en utilisant un attribut de classe comme une "valeur par défaut" pour un attribut d'instance que les instances peuvent ou non définir. Cela peut être utile dans certains cas, mais cela peut aussi être source de confusion, alors soyez prudent avec cela.

2voto

torial Points 9883

Juste une élaboration sur ce que Alex Coventry a dit, un autre Alex (Martelli) a abordé une question similaire sur le groupe de discussion comp.lang.python il y a quelques années. Il examine la différence sémantique entre ce qu'une personne voulait et ce qu'elle a obtenu (en utilisant des variables d'instance).

http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?hl=en

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