Decorator pattern in Python

The other day I was talking to a mate and former colleague of mine, he’s been doing a lot of Java and C# before but recently he got hired by a small company to do Python work. Anyway he related a funny part of the interview where he said he’d done design patterns and they asked him to explain one that he’s used. He chose Decorator. After he was done explaining the interviewer commented that surely he meant Proxy. The interviewer was wrong and my mate suspects this might be something that’s common in the Python world due to the built-in support for function/method decorators in the language. I suspect he’s right. Anyway, he showed me what he was playing with and I couldn’t help but play a bit on my own afterwards.

Here’s the class of the core object, a simple self-explanatory piece of code:

class Writer(object):
    def write(self, s):
        print s

Here’s a not very exciting example of using it:

> w = Writer()
> w.write('hello')
hello

We want to decorate it by modifying the string passed to write in different ways. First here’s a base decorator class:

class WriterDecorator(object):
    def __init__(self, wrappee):
        self.wrappee = wrappee

    def write(self, s):
        self.wrappee.write(s)

Using it is straight forward, and still not very exciting:

> wd = WriterDecorator(w)
> wd.write('hello')
hello

The constructor requires a wrappee object and the implementation of write is straight forward. Strictly speaking this class is unnecessary, but it’s convenient once we implement “real” decorators. Here’s the first one, it converts the string to upper case before passing it on down the chain:

class UpperWriter(WriterDecorator):
    def write(self, s):
        self.wrappee.write(s.upper())

This is where it gets a little more exciting, not much though:

> uw = UpperWriter(w)
> uw.write('hello')
HELLO

Here’s a nice detail about Python that I’ve never reflected over myself—constructors are inherited in Python. Here’s another decorator, one that makes the string “shouty”:

class ShoutWriter(WriterDecorator):
    def write(self, s):
        self.wrappee.write('!'.join([t for t in s.split(' ') if t]) + '!')

Now it’s getting a little more interesting, because the decorators can be combined:

> sw1 = ShoutWriter(w)
> sw1.write('hello again')
hello!again!
> sw2 = ShoutWriter(uw)
> sw2.write('hello again')
HELLO!AGAIN!

Some of these combinations are more useful than others, and if they’re used very often then it might be worth creating a convenience class for them. Here’s one that I imagine could be useful if you’re a writer for The Register:

class YahooWriter(WriterDecorator):
    def __init__(self, wrappee):
        self.wrappee = UpperWriter(ShoutWriter(wrappee))

Using it is simple:

> yw = YahooWriter(w)
> yw.write('hello again')
HELLO!AGAIN!

Well, so far it’s been child’s play and I wouldn’t have bothered writing about this unless I took this a little further. I thought something was familiar about how the convenience class worked. I vaguely remembered reading something about super being harmful and there seemed to be similarities between behaviour described there and the desired behaviour when nesting decorators. Rewriting the basic decorator classes using super like this retains their behaviour:

class UpperWriter(WriterDecorator):
    def write(self, s):
        super(UpperWriter, self).write(s.upper())


class ShoutWriter(WriterDecorator):
    def write(self, s):
        super(ShoutWriter, self).write('!'.join([t for t in s.split(' ') if t]) + '!')

What this does though is allow implementing YahooWriter like this:

class YahooWriter(UpperWriter, ShoutWriter):
    pass

I think that’s pretty cute.

Here’s where I have to stop though. I don’t know if this is even useful, is it? Maybe it has some serious draw-backs my inexperience and ignorance prevents me from seeing, does it? Has super been used like this somewhere? I’d love pointers to that code :-)

[Edited 16-06-2007 00:34 BST] Bloody hell, can’t believe I had a spelling error in the title all this time. Embarrassing really!

Share

4 Comments

  1. IIRC, the decorator uses the @ syntax before the method to decorate, like the following:

    @decorate def method: pass

    Also, I think super is not used anymore. Should have been baseclass.method .

  2. Sebastián,

    The @ syntax is for Python decorators. They are used to decorate one function (or method) with another function (or method). The decorator pattern allows one object to decorate another object. In my example the difference isn’t very clear since the core class only has one method. I can’t see any elegant way of implementing the decorator pattern using Python decorators. (I’d also like to point out that your comment adds some weight to my mate’s suggestion that there’s a confusion about the decorator pattern among Python programmers due to Python’s @-style function/method decorator.)

    I couldn’t use baseclass.method as you suggest. YahooWriter has two base classes, which one would I choose? Also, how would I make one base class “jump” over to the other? No, I need to exploit the MRO and that’s exactly what super allows me to do.

    super is not deprecated in any way. PEP 3135 will even make my example a bit nicer once it’s implemented. :-)

  3. definitely, you should correct the typo in the title, that “pyton”… I’ve been seeing it all day long on planet.haskell and I wonder why no one pointed that out…

    P!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>