Python Descriptors

Python descriptors are a way to create managed attributes. Among their many advantages, managed attributes are used to protect an attribute from changes or to automatically update the values of a dependent attribute. The methods needed to create a descriptor are __get__, __set__ and __delete__. If you define any of these methods, then you have created a descriptor.

Descriptor Protocol

The descriptor protocol is simply a set of methods a class must implement to qualify as a descriptor. There are three of them:

__get__(self, instance, owner)
__set__(self, instance, value)
__delete__(self, instance)

__get__ accesses a value stored in the object and returns it.

__set__ sets a value stored in the object and returns nothing.

__delete__ deletes a value stored in the object and returns nothing.

It is important to note that descriptors are assigned to a class, not an instance. Modifying the class overwrites or deletes the descriptor itself, rather than triggering its code.

When Descriptors Are  Needed

Consider an email attribute. Verification of the correct email format is necessary before assigning a value to that attribute. This descriptor allows email to be processed through a regular expression and its format validated before assigning it to an attribute.

In many other cases, Python protocol descriptors control access to attributes, such as protection of the name attribute.

Creating Properties

You can create a descriptor a number of ways:

  • Create a class and override any of the descriptor methods: __set__, __ get__, and __delete__. This method is used when the same descriptor is needed across many different classes and attributes, for example, for type validation.
  • Use a property type which is a simpler and more flexible way to create a descriptor.
  • Use the power of property decorators which are a combination of property type method and Python decorators.
class Person(object):
    def __init__(self):
        self._name = ''
    def name(self):
        print "Getting: %s" % self._name
        return self._name
    def name(self, value):
        print "Setting: %s" % value
        self._name = value.title()
    def name(self):
        print ">Deleting: %s" % self._name
        del self._name

More about @property decorator: