Descriptors are objects that are (usually) attributes of classes and that have any of __get__
, __set__
, or __delete__
special methods.
Data Descriptors have any of __set__
, or __delete__
These can control the dotted lookup on an instance, and are used to implement functions, staticmethod
, classmethod
, and property
. A dotted lookup (e.g. instance foo
of class Foo
looking up attribute bar
- i.e. foo.bar
) uses the following algorithm:
bar
is looked up in the class, Foo
. If it is there and it is a Data Descriptor, then the data descriptor is used. That's how property
is able to control access to data in an instance, and instances cannot override this. If a Data Descriptor is not there, then
bar
is looked up in the instance __dict__
. This is why we can override or block methods being called from an instance with a dotted lookup. If bar
exists in the instance, it is used. If not, we then
look in the class Foo
for bar
. If it is a Descriptor, then the descriptor protocol is used. This is how functions (in this context, unbound methods), classmethod
, and staticmethod
are implemented. Else it simply returns the object there, or there is an AttributeError