SoFunction
Updated on 2024-12-20

A concise guide to using the descriptor descriptor in Python

When defining an iterator, the description is an object that implements the iteration protocol, i.e., an object that implements the __iter__ method. Similarly, a descriptor is an object that implements the descriptor protocol, i.e., the __get__, __set__, and __delete__ methods.

If you look at the definition alone, it is still quite abstract. talk is cheap. look at the code:

class WebFramework(object):
  def __init__(self, name='Flask'):
     = name

  def __get__(self, instance, owner):
    return 

  def __set__(self, instance, value):
     = value


class PythonSite(object):

  webframework = WebFramework()

In [1]: 
Out[1]: 'Flask'

In [2]:  = 'Tornado'

In [3]: 
Out[3]: 'Tornado'

A class WebFramework is defined that implements the descriptor protocols __get__ and __set__, and that object (classes are objects, everything is an object) becomes a descriptor. An object that implements both __get__ and __set__ is called a data descriptor. Those that implement only __get__ are non-descriptors. The difference between the two is the priority of the dictionary relative to the instance.

If the instance dictionary has an attribute with the same name as the descriptor, it takes precedence over the profile descriptor if the descriptor is a profile descriptor, and over the attribute in the dictionary if it is a non-profile descriptor.

Descriptor Calls
For this type of magic, the method of invocation is often not directly used. Decorators, for example, need to be called with the @ symbol. Iterators are usually called during the iteration process, or using the next method. Descriptors, on the other hand, are simpler and are called when an object property is called.

In [15]: webframework = WebFramework()

In [16]: webframework.__get__(webframework, WebFramework)
Out[16]: 'Flask'

Application of Descriptors
Descriptors are useful mainly in the definition of methods and properties. Since we can re-describe the properties of a class, this magic can change some of the behavior of the class. The simplest application is to write a cache of class attributes in conjunction with a decorator, and the authors of Flask have written a werkzeug web toolkit that uses the descriptor feature to implement a cache.

class _Missing(object):
  def __repr__(self):
    return 'no value'

  def __reduce__(self):
    return '_missing'


_missing = _Missing()


class cached_property(object):
  def __init__(self, func, name=None, doc=None):
    self.__name__ = name or func.__name__
    self.__module__ = func.__module__
    self.__doc__ = doc or func.__doc__
     = func

  def __get__(self, obj, type=None):
    if obj is None:
      return self
    value = obj.__dict__.get(self.__name__, _missing)
    if value is _missing:
      value = (obj)
      obj.__dict__[self.__name__] = value
    return value


class Foo(object):
  @cached_property
  def foo(self):
    print 'first calculate'
    result = 'this is result'
    return result


f = Foo()

print   # first calculate this is result
print   # this is result

As you can see, first calculate only caches the result after it is calculated on the first call. The advantage of this is that in network programming, parsing the HTTP protocol usually parses the HTTP header into a python dictionary, which may be accessed more than once in the view function, so caching the header with a descriptor reduces redundant parsing.

Descriptors are widely used in python, usually in conjunction with decorators. Powerful magic comes with powerful responsibilities. Descriptors can also be used to "pre-compile" sql statements in ORM. If you use descriptors properly, you can make your Python code more elegant.