enhancing/wrapping an existing instance of a duck

Bruno Desthuilliers bdesth.quelquechose at free.quelquepart.fr
Mon Sep 1 21:05:57 CEST 2008

Neville Dempsey a écrit :
> What do I need to add to HTMLDecorator?
> A simpler example:
> import cgi
> class ClassX(object):
>   pass # ... with own __repr__
> class ClassY(object):
>   pass # ... with own __repr__
> inst_x=ClassX()

Why do you need to prefix your variables with 'inst_' ?

> inst_y=ClassY()
> inst_z=[ i*i for i in range(25) ]
> inst_b=True
> class HTMLDecorator(object):
>    def html(self): # an "enhanced" version of __repr__
>        return cgi.escape(self.__repr__()).join(("<H1>","</H1>"))

uppercase tags are so 1990...

More seriously, your code would be more readable (and more maintainable) 
using string formatting, ie:

    def html(self): # an "enhanced" version of __repr__
        return cgi.escape("<h1>%r</h1>" % self)

> print HTMLDecorator(inst_x).html()

(snip unexecuted code)

> Output:
> Traceback (most recent call last):
>   File "html.py", line 21, in <module>
>     print HTMLDecorator(inst_x).html()
> TypeError: default __new__ takes no parameters
> Can I simply decorate an existing instance?

For which definitions of "simply" and "decorate", and for instances of 
what ?

Some classes will let you add arbitrary attributes to either themselves 
of their instances. Some (hint: mostly builtin types) wont, because 
they've been implemented otherwise, usually for performances reasons.

If all you need is a simple enhanced "html" __repr__, just use a generic 
function - being OO doesn't mean you have to use classes everywhere 
(hint : OO stands for "object oriented", not "class oriented"). All you 
need here is:

def html(obj, tag="h1"):
     return "<%s>%s</%s>" % (tag, cgi.escape(repr(obj)), tag)

If you have more responsabilities to add, and want something generic, 
then you'll have to use composition/delegation instead of inheritance, ie:

class HTMLDecorator(object):
     def __init__(self, obj):
         # use name mangling to avoid possible name clash
         self.__obj = obj
     def html(self):
         # reuse the above generic html() func
         return html(self.__obj)
     def __getattr__(self, name):
         # trivial, always delegate - __getattr__ is
         # only called when evrything else failed
         return getattr(self.__obj, name)
     def __setattr__(self, name, value):
         # tricky : __setattr__ is *always* called
         if name == "_HTMLDecorator__obj" or name in type(self).__dict__:
             super(HTMLDecorator, self).__setattr__(name, value)
             setattr(self.__obj, name, value)

But be warned that this might be tricky (I wouldn't bet a truly generic 
implementation of the class decorator pattern is something doable), and 
that you'll have to pay the price (wrt/ perfs) for overriding 
__setattr__. As a general rule, it's better to avoid overridding 
__getattribute__ and __setattr__ whenever possible.


More information about the Python-list mailing list