2 votes

Pourquoi modifier un attribut de classe ne modifie-t-il pas un attribut d'objet en Python?

Comme illustré dans l'extrait de code ci-dessous, je ne comprends pas pleinement comment Python accède aux variables de classe.

class Test():
    var = 1

obj = Test()
obj2 = Test()
print(Test.var, '\t',obj.var, '\t', obj2.var)

obj.var += 1
print(Test.var, '\t',obj.var, '\t', obj2.var)

Test.var += 5
print(Test.var, '\t',obj.var, '\t', obj2.var)

Donne la sortie suivante:

1        1       1  # Logique
1        2       1  # Logique également
6        2       6  # Je m'attends à ce que obj.var devienne '7'

La obj.var reste la même. Cela signifie-t-il que la modification de obj.var a créé une variable spécifique à l'objet qui est désormais indépendante de l'attribut de classe var?

1voto

Uri Zarfaty Points 768

La confusion est probablement dans l'instruction obj.var += 1.

Cela revient à obj.var = obj.var + 1. Le côté droit ne peut pas trouver var sur l'objet, donc il délègue à la classe. Cependant, le côté gauche stocke le résultat sur l'objet. À partir de ce moment-là, en recherchant var sur l'objet, la délégation vers l'attribut de la classe ne se fera plus.

>>> class Test():
>>>    var = 1
>>> Test.__dict__
mappingproxy({'__module__': '__main__',
              'var': 1,
              '__dict__': ,
              '__weakref__': ,
              '__doc__': None})
>>> obj = Test()
>>> obj.__dict__
{}
>>> obj.var += 1
>>> obj.__dict__
{'var': 2}

Si vous supprimez l'attribut de l'objet, l'attribut de la classe est de nouveau exposé :

>>> Test.var += 5
>>> obj.var
2
>>> del obj.var
>>> obj.var
6

Extrait pertinent de la documentation:

Une instance de classe possède un espace de noms implémenté sous forme de dictionnaire qui est le premier endroit où les références aux attributs sont recherchées. Lorsqu'un attribut n'est pas trouvé là-bas et que la classe de l'instance a un attribut avec ce nom, la recherche se poursuit avec les attributs de la classe.


Concernant votre question suivante, voici une façon de faire ce que vous voulez en utilisant descripteurs de données, bien que je ne pense pas que ce soit très pythonique. En particulier, il est inhabituel d'avoir des descripteurs de données sur des classes (et cela peut causer des problèmes si vous n'êtes pas vigilant).

class VarDescriptorMetaclass(type):
    """Métaclasse pour permettre aux setters de descripteurs de données de fonctionner sur les types."""
    def __setattr__(self, name, value):
        setter = getattr(self.__dict__.get(name), '__set__')
        return setter(None, value) if setter else super().__setattr__(name, value)

class VarDescriptor(object):
    """Classe de descripteur de données pour prendre en charge la mise à jour de la propriété à la fois via la classe et l'instance."""
    def __init__(self, valeur_initiale):
        self.value = initial_value
    def __get__(self, obj, objtype):
        if obj and hasattr(obj, '_value'):
            return self.value + obj._value
        return self.value
    def __set__(self, obj, value):
        if obj:
            obj._value = value - self.value
        else:
            self.value = value
    def __delete__(self, obj):
        if obj and hasattr(obj, '_value'):
            del obj._value

class Test(metaclass=VarDescriptorMetaclass):
    var = VarDescriptor(initial_value=1)

Cela semble faire ce que vous voulez :

>>> obj = Test()
>>> obj.var
1
>>> obj.var += 1
>>> obj.var
2
>>> Test.var += 5
>>> Test.var
6
>>> obj.var
7

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X