2

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?

1

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, that clarified my vague intuitive understanding! One more thing that I'm wondering about is, suppose that I have objects' attributes with the same name as class attribute (they are objects' attributes because I had to modify them independently at some point). Now, is there a way to make a global change for those attributes? For example, modify the class attribute and make the objects' attributes "inherit" the changes? Or maybe is this approach wrong and I should just use different design to achieve this?
See the update, though it may be clearer (and simpler) to split the work into Test.base_var, obj.extra_var, and Test.var() which combines the two. Though you wouldn't be able to just write obj.var += 1 anymore.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.