Decorator metaclass

Carl Banks pavlovevidence at gmail.com
Fri May 23 04:07:39 EDT 2008


On May 22, 10:28 pm, thomas.karol... at googlemail.com wrote:
> Hi,
> I would like to create a Decorator metaclass, which automatically
> turns a class which inherits from the "Decorator" type into a
> decorator.
> A decorator in this case, is simply a class which has all of its
> decorator implementation inside a decorator() method. Every other
> attribute access is being proxied to decorator().getParent().
>
> Here's my attempt:

You got deep stuff going on there, chief, and some of it's wrong.
I'll try to point it out.

> -------------------------------------------------------
> from new import classobj
>
> class DecoratorType(type):
>         def __new__(cls, name, bases, dct):
>                 dct2 = {}
>                 for key, val in dct.iteritems():
>                         dct2[key] = val

First of all, you can just do dct2 = dct.copy().
Second, since you never use dct again, even copying it is unnecessary.


>                 # create a new class which will store all of the implementation
>                 impl = classobj('%sImpl'%name,(),dct2)

classobj creates an old-style class, and I'm pretty sure you don't
want to do that.  To create a new-style class, use type:

    impl = type('%sImpl'%name,(),dct)


>                 # update the old class to implement this implementation
>                 def __init__(self, *args, **dargs):
>                         object.__setattr__(self, '__impl', impl(*args, **dargs))

As your code stands now, object.__setattr__ isn't necessary here; just
using

    self.__impl = impl(*args,**dargs)

should work fine.  I'm guessing you intend to override __setattr__
later?

If you do use object.__setattr__, I suggest that you might want to
call the superclass's __setattr__ instead of object's.  I imagine in
this case the superclass will rarely want to override __setattr__
itself, but in general it's a good idea.  In this particular
circumstance, we don't yet have the class object (it doesn't come till
after calling type.__new__) but we do have the parent class.  So you
might consider changing the definition of __init__ to this:

    basecls = bases[0] if bases else object
    def __init__(self, *args, **dargs):
        basecls.__setattr__(self, '__impl', impl(*args, **dargs))

>                 def decorator(self):
>                         return object.__getattribute__(self,'__impl')

Again, consider changing it to

    def decorator(self):
        return basecls.__getattribute(self,'__impl')

>                 def __getattribute__(self, attr):
>                         if attr=="decorator":
>                                 return object.__getattribute__(self,'decorator')
>                         return getattr(object.__getattribute__(self, 'decorator')
> ().getParent(), attr)
>                 dct = {}
>                 dct['__init__'] = __init__
>                 dct['decorator'] = decorator
>                 dct['__getattribute__'] = __getattribute__
>
>                 return type.__new__(cls, name, bases, dct)
>
> class Decorator(object):
>         __metaclass__ = DecoratorType

Parenthetical: I don't normally recommend this style, since it
obscures the fact that you're using a custom metaclass to the user.
That is something the user probably would benefit from knowing, if for
no other reason than so they can make a mental note about where to
look first if something goes wrong.  I prefer to make the user use the
__metaclass__ attribute.

However, I could see it being desirable for some cases where you're
trying to be as transparent as possible, and indeed it looks as if
that's your goal here.


> class HBar(Decorator):
>         def __init__(self, number):
>                 Decorator.__init__(self)

Ok, at this point you have to ask yourself what you want to do,
because the solution you choose will involve trade-offs.

You will note that Decorator does not define __init__.  In fact,
object.__init__ will be called, which does nothing.  If you think that
all classes with DecoratorType as their metaclass will be a direct
subclass of Decorator, you can get away with not calling
Decorator.__init__ at all.

However, this can cause problems if a user wants to define their own
base class with an __init__ that does something (either by using the
__metaclass__ attribute, or by subclassing a Decorator subclass).  In
that case, you will have to make arrangements to pass the decorator
object to the superclass instead of the decorated.  This can be pretty
hairy, and it beyond the scope of this reply.

To do it completely transparently, your decorated class will probably
have to maintain a reference to its decorator, and will also have to
derive from a base class that delegates any method calls to the
superclass of the decorator.  (Phew.)

That won't be as easy as it sounds.


>                 self._number = number
>         def inc(self):
>                 self._number += 1
>         def p(self):
>                 print self._number
>
> hbar = HBar(10)
> for each in dir(hbar.decorator()):
>         print each
>
> hbar.decorator().p()
> hbar.decorator().inc()
> hbar.decorator().p()
> -------------------------------------------------------
> Unfortunately this does not work. The newly defined __init__ method
> inside __new__, does a call to impl(*args, **dargs). However, since
> the HBar.__init__ calls the Decorator.__init__ method, but the
> HBar.__init__ method no longer resides inside HBar, but rather inside
> HBarImpl (which is no longer a subtype of Decorator), the compiler
> complains that Decorator.__init__ is not being called with a Decorator
> instance as its first argument (which is true).
> I tried changing the definition of impl inside __new__ to have
> Decorator as one of its bases, but then for some reason impl(*args,
> **dargs) asks for 4 arguments (just like __new__) and I have no clue
> as to why that happens.

I believe it's happening because you mixed old-style and new-style
classes.  But it's not the right solution anyway.

> Any help on this?

Probably the best piece of advice is "Don't try to use Decorator
pattern".  :)

Seriously, you might want to see what other people have done in
similar cases.  This stuff is tricky to get right, so maybe you should
shamelessly ride the coattails of someone who already ran into all the
issues.  One example I can think of is the ZODB Persistent class (it's
a proxy class, so some of the same issues are involved).  Perhaps
searching Python cookbook for some proxy or decorator class recipes
will give you ideas.


Carl Banks



More information about the Python-list mailing list