Creating Descriptors

Any object with a __get__() method, and optionally __set__() and __delete__() methods, accepting specific parameters is said to follow the descriptor protocol. Such an object qualifies as a descriptor and can be placed inside a class's __dict__ to do something special when an attribute is retrieved, set or deleted. An empty descriptor is shown below.

Example 1.3. A simple descriptor

class Desc(object):
    "A descriptor example that just demonstrates the protocol"
    
    def __get__(self, obj, cls=None): 1
        pass

    def __set__(self, obj, val): 2
        pass

    def __delete__(self, obj): 3
        pass


1

Called when attribute is read (eg. print objectname.attrname). Here obj is the object on which the attribute is accessed (may be None if the attribute is accessed directly on the class, eg. print classname.attrname). Also cls is the class of obj (or the class, if the access was on the class itself. In this case, obj is None).

2

Called when attribute is set on an instance (eg. objectname.attrname = 12). Here obj is the object on which the attribute is being set and val is the object provided as the value.

3

Called when attribute is deleted from an instance (eg. del objectname.attrname). Here obj is the object on which the attribute is being deleted.

What we defined above is a class that can be instantiated to create a descriptor. Let's see how we can create a descriptor, attach it to a class and put it to work.

Example 1.4. Using a descriptor

class C(object):
    "A class with a single descriptor"
    d = Desc() 1
    
cobj = C()

x = cobj.d 2
cobj.d = "setting a value"  3
cobj.__dict__['d'] = "try to force a value" 4
x = cobj.d 5
del cobj.d 6

x = C.d 7
C.d = "setting a value on class" 8


1

Now the attribute called d is a descriptor. (This uses Desc from previous example.)

2

Calls d.__get__(cobj, C). The value returned is bound to x. Here d means the instance of Desc defined in 1. It can be found in C.__dict__['d'].

3

Calls d.__set__(cobj, "setting a value").

4

Sticking a value directly in the instance's __dict__ works, but...

5

is futile. This still calls d.__get__(cobj, C).

6

Calls d.__delete__(cobj).

7

Calls d.__get__(None, C).

8

Doesn't call anything. This replaces the descriptor with a new string object. After this, accessing cobj.d or C.d will just return the string "setting a value on class". The descriptor has been kicked out of C's __dict__.

Note that when accessed from the class itself, only the __get__() method comes in the picture, setting or deleting the attribute will actually replace or remove the descriptor.

Descriptors work only when attached to classes. Sticking a descriptor in an object that is not a class gives us nothing.