First Example Link to heading

This is write-up based on link for python descriptors.

Descriptors are described as

Descriptors let objects customize attribute lookup, storage, and deletion.

lets start with simplest example

class Age:
    def __get__(self, instance, owner):
        return instance.age_years * 12

    def __set__(self, instance, value):
        instance.age_years = value / 12

class Person3:
    age_months = Age()
    def __init__(self, age):
        self.age_years = age


p3 = Person3(2)

print(p3.__dict__)
print(type(p3).__dict__)
print(p3.age_months)

Here age_months is descriptor that calculates age in month dynamically. age_months is not in __dict__. but it is in type(Person).__dict__ as 'age_months': <__main__.Age object at 0x7fcbab796970>,

{'age_years': 2}
{'__module__': '__main__', 'age_months': <__main__.Age object at 0x7fcbab796970>, '__init__': <function Person3.__init__ at 0x7fcbab7aa310>, '__dict__': <attribute '__dict__' of 'Person3' objects>, '__weakref__': <attribute '__weakref__' of 'Person3' objects>, '__doc__': None}
24

from SO, the search order as follow:

bar = a.foo…

invokes a.getattribute(‘foo’) which in turn by default looks up a.dict[‘foo’] or invokes foo’s .get() if defined on A. The returned value would then be assigned to bar.

So, in example above, age_months.__get__() eventually gets called.

other ways to define descriptor Link to heading

Although the pythonic way is using __set__, __get__, built-in property class can be used to create descriptor

class Person1:
    def __init__(self, age):
        self.age_years = age

    def _set_age(self, value):
        self.age_years = value/12

    def _get_age(self):
        return self.age_years * 12

    age_months = property(fget=_get_age, fset=_set_age)

p1 = Person1(2)

print(p1.__dict__)
print(type(p1).__dict__)
print(p1.age_months)

Also, there is the decorator @property

class Person2:
    def __init__(self, age):
        self.age_years = age

    @property
    def age_months(self):
        return self.age_years  * 12

    @age_months.setter
    def age_months(self,value):
        self.age_years = value / 12


p2 = Person2(2)
print(p2.__dict__)
print(type(p2).__dict__)
print(p2.age_months)