Python – Why Python descriptors work with class-level properties but not instance-level attributes

Why Python descriptors work with class-level properties but not instance-level attributes… here is a solution to the problem.

Why Python descriptors work with class-level properties but not instance-level attributes

Why Python descriptors work with class-level properties but not instance-level properties.

class PropDescriptor:

def __init__(self,*args):
        print("Init {} {}".format(type(self),args))
        self.value = 0

def __get__ (self,instance,owner):
        print("get using descriptor")
        return instance.instance_att

def __set__(self, instance, value):
        print("set using descriptor")
        instance.instance_att = value

class TestClass:
    class_att = PropDescriptor()

def __init__(self):
        self.instance_att = PropDescriptor()

t = TestClass()
print("set instance level...")
t.instance_att = 3

print("\nget instance level...")
print(t.instance_att)

print("\nset class level...")
t.class_att = 4

print("\nget class level...")
print(t.class_att)

Output:

Init <class '__main__. PropDescriptor'> ()
Init <class '__main__. PropDescriptor'> ()
set instance level...

get instance level...
3

set class level...
set using descriptor

get class level...
get using descriptor
4

It looks like the descriptor is not used for instance_att.

I found this identical question, but it doesn’t really answer the question, and all the answers refer to the second question in the post.

The Python documentation specifies:

Instance Binding

If binding to an object instance, a.x is transformed

into the call: type(a).__dict__['x'].__get__(a, type(a)).

But:
The first part of the (my code equivalent) statement type(t).__dict__[“instance_att”], throws KeyError as type(t).__dict__ There is no such attribute. It is an instance-level att. Isn’t it?

What am I missing?

Solution

I’m a bit late to reply now, but I’ll try to explain. The answer to both parts of the question lies in the way Python internally implements property finding and setting.

1。 Why not call descriptors for instance-level properties?

Once you understand how a.x = somevalue is internally converted, the answer to this question is simple. When you set a property using the dot operator, e.g. a.x=“somevalue” it is converted to a.__setattribute__(x, somevalue) and further converted to type(a).__dict__[‘x’].__set__(x, somevalue) .

Now consider your example self.instance_att is defined in __init__, so it doesn’t exist in type(a).__dict__ which is a dictionary of properties defined at the class level, so the descriptor’s __set__ is never called.

2。 Why are descriptors defined only at the class level?

I’m not 100% sure, but this is my understanding because of how __getattribute__ and __setattribute__ are implemented, it won’t work at all if you define descriptors at the instance level (as described above), so they are only defined as class properties.

Also, I believe that for a descriptor to work with an instance property, you have to override __getattribute__ and __setattribute__ for that class. This is what the simplest version of the overridden version __getattribute__ looks like.

def __getattribute__(self, attr):
    attr_val = super().__getattribute__(attr)
    attr_type = type(attr_val)
    if hasattr(attr_type, '__get__'):
        return attr_type.__get__(attr_val, self, type(self))
    return attr_val

This is the same demo

$ python3 -i instance_descriptors.py
>>> t = TestClass()
>>> t.instance_att
get using descriptor
200
>>> 

In a similar way, you also need to override the __setattribute__ for the property settings to take effect.

Hope this helps!

I highly recommend reading the official Python documentation for the following descriptor to fully understand the concept.

Descriptors howto

Related Problems and Solutions