web-dev-qa-db-fra.com

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

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?

125
Dan Homerick

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    
[]
136
Alex Coventry

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.

35
Peter Shinners

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

 enter image description here

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
    
25
zangw

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 5s 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.

19
abarnert

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

2
torial

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 .

Explique

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

0
SimonShyu