Quelle est la différence entre les attributs de classe et d'instance?
Y a-t-il une distinction significative entre:
class A(object):
foo = 5 # some default value
Vs.
class B(object):
def __init__(self, foo=5):
self.foo = foo
Si vous créez beaucoup d'instances, y a-t-il une différence de performance ou d'espace pour les deux styles? Lorsque vous lisez le code, considérez-vous que la signification des deux styles est significativement différente?
6 réponses
Au-delà des considérations de performance, il existe une différence significative sémantique. Dans le cas de l'attribut de classe, il n'y a qu'un seul objet visé. Dans l'instance de l'attribut défini à l'instanciation, il peut y avoir plusieurs objets visés. 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
[]
La différence est que l'attribut de la classe est partagée par toutes les instances. L'attribut sur une instance est unique à cette instance.
S'ils proviennent de C++, les attributs de la classe ressemblent plus à des variables membres statiques.
Voici un très bon post - , et résumé ci-dessous.
class Bar(object):
## No need for dot syntax
class_var = 1
def __init__(self, i_var):
self.i_var = i_var
## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = MyClass(2)
## Finds i_var in foo's instance namespace
foo.i_var
## 2
## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1
Et sous forme visuelle
Affectation d'attribut de classe
-
Si un attribut de classe est défini en accédant à la classe, il remplace 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 remplace la valeur uniquement pour cette instance . Cela remplace essentiellement le class variable et la transforme en une variable d'instance disponible, intuitivement, uniquement pour cette instance .
foo = Bar(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 Bar.class_var ## 1
Quand utiliseriez-vous l'attribut de classe?
-
stocker les constantes . Comme les attributs de classe sont accessibles en tant qu'attributs de la classe elle-même, il est souvent agréable 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 des valeurs par défaut. Comme un exemple trivial, nous pourrions créez une liste délimitée (c'est-à-dire une liste qui ne peut contenir qu'un certain nombre d'éléments ou moins) et choisissez d'avoir un plafond 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("Too many elements") self.data.append(e) MyClass.limit ## 10
Puisque les gens dans les commentaires ici et dans deux autres questions marquées comme dups semblent tous être confus à ce sujet de la même manière, je pense qu'il vaut la peine d'ajouter une réponse supplémentaire au-dessus deAlex 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 soient 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é object()
au lieu de, disons, 5
, c'est pour éviter de rencontrer deux autres problèmes que je ne veux pas aborder ici; pour deux raisons différentes, les 5
entièrement créés séparément peuvent finir par être la même instance du nombre 5
. Mais tout à fait séparément-créé object()
S ne peut 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 notez que cela n'affecte pas b.foo
là non plus .
a.foo = 5
est juste faire a.foo
dans un nom de 5
. Cela n'affecte pas b.foo
, ou tout autre nom pour l'ancienne valeur à laquelle a.foo
faisait référence.* C'est un peu délicat que nous créions un attribut d'instance qui cache un attribut de classe,** mais une fois que vous obtenez cela, rien de compliqué ne se passe ici.
Espérons qu'il est maintenant évident pourquoi Alex a utilisé une liste: le fait que vous puissiez muter une liste signifie qu'il est plus facile de montrer que deux variables nomment la même liste, et aussi signifie qu'il est plus important dans le code de la vie réelle 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 qu'en Python, les valeurs ne sont pas stockées dans les variables. Les valeurs vivent dans value-land, seules, les variables ne sont que des noms pour les valeurs, et l'affectation crée simplement un nouveau nom pour une valeur. Si cela vous aide, pensez à chaque variable Python comme un shared_ptr<T>
au lieu d'un T
.
** Certains les gens en profitent en utilisant un attribut de classe comme "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 il peut aussi être source de confusion, donc soyez prudent avec elle.
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 des années. Il examine la différence sémantique entre ce qu'une personne a voulu et ce qu'elle a obtenu (en utilisant des variables d'instance).
Http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?hl=en
Il y a encore une situation.
Les attributs de classe et d'instance sont descripteur .
# -*- encoding: utf-8 -*-
class RevealAccess(object):
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
return self.val
class Base(object):
attr_1 = RevealAccess(10, 'var "x"')
def __init__(self):
self.attr_2 = RevealAccess(10, 'var "x"')
def main():
b = Base()
print("Access to class attribute, return: ", Base.attr_1)
print("Access to instance attribute, return: ", b.attr_2)
if __name__ == '__main__':
main()
Ci-dessus produira:
('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)
Le même type d'accès à l'instance via la classe ou l'instance renvoie un résultat différent!
Et j'ai trouvé dans C. PyObject_GenericGetAttr définition ,et un grand post .
Expliquer
Si l'attribut se trouve dans le dictionnaire des classes qui le composent. les objets MRO, alors vérifiez si l'attribut recherché pointe vers un descripteur de données (ce qui n'est rien de plus qu'une classe implémentant à la fois les méthodes
__get__
et__set__
). Si c'est le cas, résolvez la recherche d'attribut en appelant la méthode__get__
du descripteur de données (lignes 28-33).