Delegating Automatically as an Alternative to Inheritance

Credit: Alex Martelli

Problem

You’d like to inherit from a built-in type, but you are using Python 2.1 (or earlier), or need a semantic detail of classic classes that would be lost by inheriting from a built-in type in Python 2.2.

Solution

With Python 2.2, we can inherit directly from a built-in type. For example, we can subclass file with our own new-style class and override some methods:

class UppercaseFile(file):
    def write(self, astring):
        return file.write(self, astring.upper(  ))
    def writelines(self, strings):
        return file.writelines(self, map(string.upper,strings))
upperOpen = UppercaseFile

To open such a file, we can call upperOpen just like a function, with the same arguments as the built-in open function. Because we don’t override _ _init_ _, we inherit file’s arguments, which are the same as open’s.

If we are using Python 2.1 or earlier, or if we need a classic class for whatever purpose, we can use automatic delegation:

class UppercaseFile:
    # Initialization needs to be explicit
    def _ _init_ _(self, file):
        # NOT self.file=file, to avoid triggering _ _setattr_ _ 
        self._ _dict_ _['file'] = file

    # Overrides aren't very different from the inheritance case:
    def write(self, astring):
        return self.file.write(astring.upper(  ))
    def writelines(self, strings):
        return self.file.writelines(map(string.upper,strings))

    # Automatic delegation is a simple and short boilerplate:
    def _ _getattr_ _(self, attr):
        return getattr(self.file, attr)
    def _ _setattr_ _(self, attr, value):
        return setattr(self.file, attr, value)

def upperOpen(*args, **kwds):
    return UppercaseFile(open(*args, **kwds))

In this variant, upperOpen is called just as before but it separates the generation of the file object internally (done via the built-in open function) and its wrapping into the automatically delegating class (UppercaseFile).

Discussion

Automatic delegation, which the special methods _ _getattr and _ _setattr_ _ let us perform so smoothly, is a powerful and general technique. In this recipe, we show how to use it to get an effect that is almost indistinguishable from subclassing a built-in type, but in a way that also works with Python 2.1 and earlier. This technique also produces a classic class, just in case we want the classic object model’s semantics even in newer versions of Python. Performance isn’t quite as good as with real inheritance, but we get better flexibility and finer-grained control as compensation.

The fundamental idea is that each instance of our class holds an instance of the type we are wrapping (i.e., extending and/or tweaking). Whenever client code tries to get an attribute from an instance of our class, unless the attribute is specifically defined there (e.g., the write and writelines methods in this recipe), _ _getattr_ _ transparently shunts the request to the wrapped instance. In Python, methods are also attributes, accessed in just the same way, so we don’t need to do anything more to access methods—the approach used to access data attributes works for methods just as well. _ _setattr_ _ plays a similar role when client code sets an attribute. Remember that to avoid triggering _ _setattr_ _ from inside the methods you code, you must set values in self._ _dict_ _ explicitly. While Python calls _ _getattr_ _ only for attributes it does not find in the usual way, it calls _ _setattr_ _ for every attribute that is set (except for a few special ones such as _ _dict_ _ and _ _class_ _, held in the object itself and not in its dictionary).

Note that wrapping by automatic delegation does not work well with client or framework code that, one way or another, does type-testing. In such cases, it is the client or framework code that is breaking polymorphism and should be rewritten. Remember not to use type-tests in your own client code, as you probably do not need them anyway. See Recipe 5.11 for better alternatives.

In Python 2.2, you’ll use automatic delegation less often, since you don’t need it for the specific purpose of subclassing built-ins. However, delegation still has its place—it is just a bit farther from the spotlight than in 2.1 and earlier. Although the new-style object model (which you get by subclassing built-ins) is almost always preferable, there are a few cases in which you should use classic classes because they are even more dynamic than new-style classes. For example, if your program needs to change an instance’s _ _class_ _ on the fly, this is always allowed for instances of classic classes, but subject to constraints for instances of new-style classes. More importantly, delegation is generally more flexible than inheritance, and sometimes such flexibility is invaluable. For example, an object can delegate to different subobjects over time or even all at once (see Recipe 5.21), and inheritance doesn’t offer anything comparable.

See Also

Recipe 5.11 and Recipe 5.21; PEP 253 (http://www.python.org/peps/pep-0253.html) describes in detail what there is to know about subtyping built-in types.

Get Python Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.