From the documentation.
For objects, the machinery is in
object.__getattribute__()
which transformsb.x
intotype(b).__dict__['x'].__get__(b, type(b))
.
That is, the attribute lookup on an instance (b
) is converted into a descriptor call on the class (type(b)
). Descriptors operate at the class level.
As for why this is true, it's because descriptors are basically a way to do method-like work (i.e., call a method) on attribute lookup. And methods are essentially class-level behavior: you generally define the methods you want on a class, and you don't add extra methods to individual instances. Doing descriptor lookup on an instance would be like defining a method on an instance.
Now, it is possible to assign new methods to instances, and it's also possible to get descriptors to work on instances of a particular class. You just have to do extra work. As the documentation quote above says, the machinery is in object.__getattribute__
, so you can override it by defining a custom __getattribute__
on your class:
class Foo(object):
def __getattribute__(self, attr):
myDict = object.__getattribute__(self, '__dict__')
if attr in myDict and hasattr(myDict[attr], '__get__'):
return myDict[attr].__get__(self, type(self))
else:
return super(Foo, self).__getattribute__(attr)
class D(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
pass
def __get__(self, obj, objtype=None):
return 5
And then:
>>> f = Foo()
>>> f.x = D()
>>> f.x
5
So if you feel the need to do this, you can make it happen. It's just not the default behavior, simply because it's not what descriptors were designed to do.