Descriptors are objects with method __get__, __set__, and __del__. These when used as class attributes, they add the behavior to the attribute that gets triggered when they accessed.

For example,

class DefaultAge:
    def __get__(self, obj, objtype=None):
        return 12
 
 
class Person:
    age = DefaultAge()

We access age attribute on Person class, python triggers __get__ method of descriptor.

>>> Person.age
12

How it works?

When python encounters a descriptor as a class attribute, the invocation happens like below,

Person.__dict__['age'].__get__(None, Person)

This also explains __get__ parameters where self is the descriptor itself, obj is the class instance (explained in subsequent section) and objtype which is class.

If we can Person instance and then try to access age attribute,

>>> person = Person()
>>> person.age
12

the invocation happens like below,

type(person).__dict__['age'].__get__(person, type(person))

Use cases

Most common use cases of descriptors are classmethod, staticmethod, property.

Refer this notebook which contains custom implementations for classmethod and staticmethod.

References

  1. https://docs.python.org/3/glossary.html#term-descriptor
  2. https://docs.python.org/3/reference/datamodel.html#descriptors
  3. https://docs.python.org/3/howto/descriptor.html#descriptorhowto