Reputation: 71
So as illustrated in the code snippet below, I can't fully understand how Python is accessing class variables.
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)
Gives the following output:
1 1 1 # Makes sense
1 2 1 # Also makes sense
6 2 6 # I would expect obj.var to become '7'
The obj.var
stays the same. Does that mean, that modifying obj.var
created an object specific variable that is now independent of the class attribute var
?
Upvotes: 2
Views: 1112
Reputation: 1904
The confusion is probably in the obj.var += 1
instruction.
This is equivalent to obj.var = obj.var + 1
. The right hand side can't find var
on the object so delegates to the class. The left hand side, however, stores the result on the object. From that point onward, looking up var
on the object will no longer delegate to the class attribute.
>>> class Test():
>>> var = 1
>>> Test.__dict__
mappingproxy({'__module__': '__main__',
'var': 1,
'__dict__': <attribute '__dict__' of 'Test' objects>,
'__weakref__': <attribute '__weakref__' of 'Test' objects>,
'__doc__': None})
>>> obj = Test()
>>> obj.__dict__
{}
>>> obj.var += 1
>>> obj.__dict__
{'var': 2}
If you delete the object attribute then the class attribute is exposed again:
>>> Test.var += 5
>>> obj.var
2
>>> del obj.var
>>> obj.var
6
Relevant bit from the docs:
A class instance has a namespace implemented as a dictionary which is the first place in which attribute references are searched. When an attribute is not found there, and the instance’s class has an attribute by that name, the search continues with the class attributes.
Regarding your follow-on question, here is one way to do what you want using data descriptors, though I don't think it's very Pythonic. In particular, it's unusual to have data descriptors on classes (and may break things if you're not careful).
class VarDescriptorMetaclass(type):
"""Metaclass to allow data descriptor setters to work on 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):
"""Data descriptor class to support updating property via both class and instance."""
def __init__(self, initial_value):
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)
This seems to do what you want:
>>> obj = Test()
>>> obj.var
1
>>> obj.var += 1
>>> obj.var
2
>>> Test.var += 5
>>> Test.var
6
>>> obj.var
7
Upvotes: 1