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?

123
demandé sur martineau 2008-10-16 04:23:41

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    
[]
135
répondu Alex Coventry 2008-10-16 01:26:53

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.

35
répondu Peter Shinners 2008-10-16 08:16:28

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

entrez la description de l'image ici

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
    
24
répondu zangw 2015-12-07 03:59:08

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 5entiè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 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.

19
répondu abarnert 2017-05-23 11:55:07

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

2
répondu torial 2012-11-15 15:46:06

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).

0
répondu SimonShyu 2017-06-23 10:02:25