Welcome, guest | Sign In | My Account | Store | Cart
2

Python classes cannot inherit from any type, just from other classes. However, automatic delegation (via __getattr__ and __setattr__) can provide pretty much the same functionality as inheritance (without such limits, and with finer-grained control).

Python, 30 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# ***if*** File _was_ a class...:
class UppercaseFile(File):
    # we could just override what we need to:
    def write(self, astring):
        return File.write(self, astring.upper())
    def writelines(self, strings):
        return File.writelines(self, map(string.upper,strings))
# ...arranging for an upperOpen(...) might not be quite trivial...

# but, File _isn't_ a class, so...:
class UppercaseFile:
    # initialization is explicit
    def __init__(self, file):
        # not self.file=file, to avoid triggering __setattr__
        self.__dict__['file'] = file

    # 'overrides' aren't very different:
    def write(self, astring):
        return self.file.write(self, astring.upper())
    def writelines(self, strings):
        return self.file.writelines(self, map(string.upper,strings))

    # automatic delegation isn't too difficult, either:
    def __getattr__(self, attr):
        return getattr(self.file, attr)
    def __setattr__(self, attr, value):
        return setattr(self.file, attr, value)        
# ...and upperOpen(whatever) is trivial indeed:
def upperOpen(*args):
    return UppercaseFile(open(*args))

The split between 'class' and 'type' in Python is often bemoaned -- in particular, it does not allow classes we write to inherit from builtin types we might easily want to extend and tweak, such as, e.g., the file-object type.

However, it ain't so bad...! We can easily code a class each of whose instances holds an instance of the type we're "wrapping" (extending and/or tweaking); thanks to __getattr__ and __setattr__, it's easy to arrange for anything we don't want to have know about to be automatically delegated to the instance we're holding. The recipe exemplifies this through an UppercaseFile class that arranges for anything written to be uppercased first, while otherwise transparently delegating anything else to the file object it holds; but this technique really comes in handy for ALL sort of wrappers.

(Not JUST wrappers over built-in types, actually; delegation, while slightly less automatic than inheritance, IS more powerful in terms of fine-grained control on operations of interest -- inheritance will most often suffice, and it's easier to use [and may perform better], but it's better to also keep automatic delegation in one's bag of tricks, just in case...).

Note: wrapping will NOT, of course!, work with client code that, one way or another, tests with "isinstance(anobj, atype)" [or, worse, checks that "type(anobj)==atype" -- SHUDDER!] -- but then, in such cases it is the client code that's breaking polymorphism, and should be rewritten...! _Point to remember_: DO NOT code type-tests in your own client-code -- you do NOT really need them anyway!-)

4 comments

John E. Barham 12 years, 10 months ago  # | flag

Python 2.2 allows subclassing of built-in types. The title says it all: Python 2.2 allows subclassing of built-in types, so classic inheritance vs. delegation can be used in these cases. In particular, the example given can be simplified by creating a subclass of the built-in file type.

E.g.:

class UpperCaseFile(file):
    def write(self, s):
        return file.write(self, s.upper())

    def writelines(self, strings):
        ...
Norbert Klamann 12 years, 3 months ago  # | flag

Unfortunately this doesn't work. the __getattr__ recurses.

It should look like so:

def __getattr__(self, attr):
    return getattr(self.__dict__['file'], attr)
def __setattr__(self, attr, value):
    return setattr(self.__dict__['file', attr, value)
Gabriel Genellina 11 years, 7 months ago  # | flag

no recursion involved. __getattr__ is called only after the standard mechanisms are unsuccessful, so self.file doesn't invoke a recursive call.

Martin Miller 1 year, 3 months ago  # | flag

This recipe is still useful for extending things that aren't built-in types or classes, like the objects of type _csv.reader returned from csv.reader() calls.

Add a comment

Sign in to comment