Descriptor

Snippets about descriptors.

descriptors 101

What is descriptor:

  • __get__
  • __set__
  • __delete__
  • Common descriptors: function, property

Demo:

>>> class Demo(object):
...     def hello(self):
...         pass
...
>>> Demo.hello
<unbound method Demo.hello>
>>> Demo.__dict__['hello']
<function hello at 0x102b17668>
>>> Demo.__dict__['hello'].__get__(None, Demo)
<unbound method Demo.hello>
>>>
>>> Demo().hello
<bound method Demo.hello of <__main__.Demo object at 0x102b28550>>
>>> Demo.__dict__['hello'].__get__(Demo(), Demo)
<bound method Demo.hello of <__main__.Demo object at 0x102b285d0>>

data and non-data descriptors

If an object defines both __get__ and __set__, it is considered a data descriptor. Descriptors that only define __get__ are called non-data descriptors.

Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance’s dictionary. If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance’s dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.

Demo:

class Demo(object):

    def __init__(self):
        self.val = 'demo'

    def __get__(self, obj, objtype):
        return self.val


class Foo(object):

    o = Demo()
>>> f = Foo()
>>> f.o
'demo'
>>> f.__dict__['o'] = 'demo2'
>>> f.o
'demo2'

Now we add __set__:

class Demo(object):

    def __init__(self):
        self.val = 'demo'

    def __get__(self, obj, objtype):
        return self.val

    def __set__(self, obj, val):
        self.val = val
>>> f = Foo()
>>> f.o
'demo'
>>> f.__dict__['o'] = 'demo2'
>>> f.o
'demo'
>>> f.o = 'demo3'
>>> f.o
'demo3'

better way to write property

Let’s just see the code:

class Foo(object):

    def _get_thing(self):
        """Docstring"""
        return self._thing

    def _set_thing(self, value):
        self._thing = value

    thing = property(_get_thing, _set_thing)
    del _get_thing, _set_thing

cached property

Just grab it from django source:

class cached_property(object):
    """
    Decorator that creates converts a method with a single
    self argument into a property cached on the instance.
    """
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, type):
        res = instance.__dict__[self.func.__name__] = self.func(instance)
        return res