Python descriptors have been around a long time—they were introduced way back in Python 2.2. But they're still not widely understood or used. This article shows how to create descriptors and presents three examples of use. All three examples run under Python 3.0, although the first two can be back-ported to any Python version from 2.2 onward simply by changing each class definition to inherit object, and by replacing uses of str.format() with the % string formatting operator. The third example is more advanced, combining descriptors with class decorators—the latter introduced with Python 2.6 and 3.0—to produce a uniquely powerful effect.
What Are Descriptors?
A descriptor is a class that implements one or more of the special methods, __get__(), __set__(), and __delete__(). Descriptor instances are used to represent the attributes of other classes. For example, if we had a descriptor class called MyDescriptor, we might define a class that used it like this:
class MyClass: a = MyDescriptor("a") b = MyDescriptor("b")
(In Python 2.x versions, we would write class MyClass(object): to make the class a new-style class.) The MyClass class now has two instance variables, accessible as self.a and self.b in MyClass objects. How these instance variables behave depends entirely on the implementation of the MyDescriptor class—and this is what makes descriptors so versatile and powerful. In fact, Python itself uses descriptors to implement properties and static methods.
Now we'll look at three examples that use descriptors for three completely different purposes so that you can start to see what can be achieved with descriptors. The first two examples show read-only attributes; the third example shows editable attributes. None of the examples covers deletable attributes (using __delete__()), since use cases are rather rare.