Question

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?

Était-ce utile?

La solution

Au-delà des considérations de performances, il existe une différence sémantique significative. Dans le cas de l'attribut de classe, un seul objet est mentionné. Dans l'instance-attribut-à-instanciation, il peut être fait référence à plusieurs objets. 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    
[]

Autres conseils

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

Si vous venez de C ++, les attributs de la classe ressemblent davantage à des variables membres statiques.

Voici un très bon post , et récapitulez-le comme suit: 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

entrer description de l'image ici

Attribution d'attributs 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, 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 des constantes . Comme les attributs de classe peuvent être accédés en tant qu’attributs de la classe elle-même, il est souvent agréable de les utiliser pour stocker les constantes spécifiques à la classe et à l’échelle de 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
    

Étant donné que 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 que cela vaut la peine d'ajouter une réponse supplémentaire au-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 des raisons, les a.foo.append(5) s entièrement créés séparément peuvent finir par être la même instance du nombre b.foo. Mais les a.foo = 5 s entièrement créés séparément ne peuvent pas.)

Alors, pourquoi a.foo dans l'exemple d'Alex affecte-t-il shared_ptr<T>, mais pas T dans mon exemple? Dans l'exemple d'Alex, essayez <=> et remarquez que cela ne concerne pas <=> la la .

<=> est en train de transformer <=> en un nom pour <=>. Cela n'affecte pas <=>, ni aucun autre nom pour l'ancienne valeur à laquelle <=> faisait référence. * Il est un peu délicat de créer un attribut d'instance qui cache un attribut de classe, ** comprendre que rien de compliqué ne 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 issues d'un langage comme le C ++ est que, en 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 <=> au lieu d'un <=>.

** Certaines personnes profitent de cela 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.

Il y a une autre 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 sera affiché:

('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 la définition de c.PyObject_GenericGetAgTAttr & # 65292; et un excellent publier .

Expliquez

  

Si l'attribut se trouve dans le dictionnaire des classes qui le composent.   les objets MRO, puis vérifiez si l'attribut recherché recherche un descripteur de données (qui n'est rien de plus qu'une classe implémentant à la fois les méthodes __get__ et __set__).   Si tel est le cas, résolvez la recherche d'attribut en appelant la méthode <=> du descripteur de données (lignes 28 & # 8211; 33).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top