Descriptors In The Box

Before you rush to the mall and get yourself some expensive descriptors, note that Python ships with some very useful ones that can be found by simply looking in the box.

Example 1.7. Built-in descriptors

class HidesA(object):

    def get_a(self):
        return self.b - 1

    def set_a(self, val):
        self.b = val + 1

    def del_a(self):
        del self.b

    a = property(get_a, set_a, del_a, "docstring") 1


    def cls_method(cls):
        return "You called class %s" % cls

    clsMethod = classmethod(cls_method) 2


    def stc_method():
        return "Unbindable!"

    stcMethod = staticmethod(stc_method) 3



1

A property provides an easy way to call functions whenever an attribute is retrieved, set or deleted on the instance. When the attribute is retrieved from the class, the getter method is not called but the property object itself is returned. A docstring can also be provided which is accessible as HidesA.a.__doc__.

2

A classmethod is similar to a regular method, except that is passes the class (and not the instance) as the first argument to the function. The remaining arguments are passed through as usual. It can also be called directly on the class and it behaves the same way. The first argument is named cls instead of the traditional self to avoid confusion regarding what it refers to.

3

A staticmethod is just like a function outside the class. It is never bound, which means no matter how you access it (on the class or on an instance), it gets called with exactly the same arguments you pass. No object is inserted as the first argument.

As we saw earlier, Python functions are descriptors too. They weren't descriptors in earlier versions of Python (as there were no descriptors at all), but now they fit nicely into a more generic mechanism.

A property is always a data-descriptor, but not all arguments are required when defining it.

Example 1.8. More on properties

class VariousProperties(object):

    def get_p(self):
        pass

    def set_p(self, val):
        pass

    def del_p(self):
        pass

    allOk = property(get_p, set_p, del_p)  1

    unDeletable = property(get_p, set_p) 2

    readOnly = property(get_p) 3


1

Can be set, retrieved, or deleted.

2

Attempting to delete this attribute from an instance will raise AttributeError.

3

Attempting to set or delete this attribute from an instance will raise AttributeError.

The getter and setter functions need not be defined in the class itself, any function can be used. In any case, the functions will be called with the instance as the first argument. Note that where the functions are passed to the property constructor above, they are not bound functions anyway.

Another useful observation would be to note that subclassing the class and redefining the getter (or setter) functions is not going to change the property. The property object is holding on to the actual functions provided. When kicked, it is going to say "Hey, I'm holding this function I was given, I'll just call this and return the result.", and not "Hmm, let me look up the current class for a method called 'get_a' and then use that". If that is what one wants, then defining a new descriptor would be useful. How would it work? Let's say it is initialized with a string (i.e. the name of the method to call). On activation, it does a getattr() for the method name on the class, and use the method found. Simple!

Classmethods and staticmethods are non-data descriptors, and so can be hidden if an attribute with the same name is set directly on the instance. If you are rolling your own descriptor (and not using properties), it can be made read-only by giving it a __set__() method but raising AttributeError in the method. This is how a property behaves when it does not have a setter function.