Existe-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, existe-t-il une différence de performances ou d'espace requis pour les deux styles? Lorsque vous lisez le code, considérez-vous que la signification des deux styles est très différente?
Au-delà des considérations de performance, il existe une différence significative sémantique . Dans le cas de l'attribut de classe, un seul objet est mentionné. Dans l'instance-attribut-à-instanciation, il peut y avoir plusieurs objets référencé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 sur la classe est partagé par toutes les instances. L'attribut sur une instance est unique pour cette instance.
Si elles proviennent de C++, les attributs de la classe ressemblent davantage à des variables membres statiques.
Voici un très bon post , et résumez-le comme 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 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, la valeur uniquement pour cette instance sera remplacée. Cela remplace essentiellement la variable de classe 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?
Stockage de 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. À titre d’exemple trivial, nous pourrions créer une liste limitée (c’est-à-dire une liste ne pouvant contenir qu’un certain nombre d’éléments ou moins) et choisir un plafond de 10 éléments par défaut.
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 personnes dans les commentaires ici et dans deux autres questions marquées comme des dupes semblent toutes confondues à ce sujet de la même manière, je pense qu'il vaut la peine d'ajouter une réponse supplémentaire par-dessus de Alex Coventry .
Le fait qu'Alex assigne une valeur de type mutable, comme une liste, n'a rien à voir avec le partage ou non des choses. 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 voudrais pas aborder ici; pour deux raisons différentes, des 5
s créés de manière totalement séparée peuvent aboutir étant la même instance du nombre 5
. Mais object()
s entièrement créés 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 pas a.foo = 5
dans mon exemple? Eh bien, essayez a.foo = 5
dans l'exemple d'Alex et remarquez que cela n'affecte pas b.foo
ici ni.
a.foo = 5
est en train de transformer a.foo
en un nom pour 5
. Cela n'affecte pas b.foo
, ni aucun autre nom pour l'ancienne valeur à laquelle a.foo
se référait. * Il est un peu délicat de créer un attribut d'instance qui masque un attribut de classe, ** compliqué se passe ici.
J'espère que la raison pour laquelle Alex a utilisé une liste est maintenant évidente: le fait que vous puissiez modifier une liste signifie qu'il est plus facile de montrer que deux variables nomment la même liste, mais 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, dans Python, les valeurs ne sont pas stockées dans des variables. Les valeurs vivent dans value-land, seules, les variables ne sont que des noms de valeurs et l'affectation crée simplement un nouveau nom pour une valeur. Si cela vous aide, considérez chaque variable Python comme un shared_ptr<T>
au lieu d'un T
.
** Certaines personnes en tirent parti 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 cela peut aussi être déroutant, alors faites attention.
Juste pour préciser ce qu’a dit Alex Coventry, un autre Alex (Martelli) a répondu à une question similaire sur les groupes de newsgrouper comp.lang.python
. Il examine la différence sémantique entre ce qu'une personne souhaitait et ce qu'il a obtenu (en utilisant des variables d'instance).
http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?hl=fr
Il y a une autre situation.
Les attributs de classe et d'instance sont Descriptor.
# -*- 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 __== '__main__':
main()
Ci-dessus va sortir:
('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)
Le même type d'accès à une instance via une classe ou une instance renvoie un résultat différent!
Et j'ai trouvé dans c.PyObject_GenericGetAttr definition et un grand post .
Si l'attribut se trouve dans le dictionnaire des classes qui composent . les objets MRO, puis vérifiez si l'attribut recherché recherche un descripteur de données (qui n'est rien d'autre qu'une classe implémentant les méthodes
__get__
et__set__
) . Si tel est le cas, résolvez la recherche d'attribut en appelant la méthode__get__
du descripteur de données (lignes 28 à 33).