[Python-Dev] Class decorators vs metaclasses

Eyal Lotem eyal.lotem at gmail.com
Sat Nov 5 12:27:55 CET 2005

On 11/5/05, Alex Martelli <aleaxit at gmail.com> wrote:
> On 11/4/05, Eyal Lotem <eyal.lotem at gmail.com> wrote:
> > I have a few claims, some unrelated, and some built on top of each
> > other.  I would like to hear your responses as to which are
> > convincing, which arne't, and why. I think that if these claims are
> > true, Python 3000 should change quite a bit.
> >
> > A. Metaclass code is black magic and few understand how it works,
> > while decorator code is mostly understandable, even by non-gurus.
> I disagree.  I've held many presentations and classes on both
> subjects, and while people may INITIALLY feel like metaclasses are
> black magic, as soon as I've explained it the fear dissipates.  It all
> boils down do understanding that:
> class Name(Ba,Ses): <<body>>
> means
> Name = suitable_metaclass('Name', (Ba,Ses), <<dict-built-by-body>>)
> which isn't any harder than understanding that
> @foo(bar)
> def baz(args): ...
> means
> def baz(args): ...
> baz = foo(bar)(baz)

I disagree again. My experience is that metaclass code is very hard to
understand. Especially when it starts doing non-trivial things, such
as using a base metaclass class that is parametrized by metaclass
attributes in its subclasses.  Lookups of attributes in the base
metaclass methods is mind boggling (is it searching them in the base
metaclass, the subclass, the instance [which is the class]?).  The
same code would be much easier to understand with class decorators.

> > B. One of Decorators' most powerful features is that they can
> > mixed-and-matched, which makes them very powerful for many purposes,
> > while metaclasses are exclusive, and only one can be used.  This is
> Wrong.  You can mix as many metaclasses as you wish, as long as
> they're properly coded for multiple inheritance (using super, etc) --
> just inherit from them all.  This is reasonably easy to automate (see
> the last recipe in the 2nd ed of the Python Cookbook), too.

Multiple inheritence is an awful way to mix class fucntionalities
though. Lets take a simpler example.  Most UT frameworks use a
TestCase base class they inherit from to implement setup, tearDown,
and then inherit from it again to implement the test itself.  I argue
this is a weak approach, because then mixing/matching setups is
difficult.  You would argue this is not the case, because of the
ability to multiply-inherit from test cases, but how easy is the
equivalent of:

def my_test():
  # the blah setup and bleh other setup are up and usable here,
  # and will be "torn down" at the end of this test

The equivalent of this requires a lot more work and violating DRY. 
Creating a specific function to multiply inherit from TestCases is a
possible solution, but it is much more conceptually complex, and needs
to be reimplemented in the next scenario (Metaclasses for example).

> > especially problematic as some classes may assume their subclasses
> > must use their respective metaclasses.  This means classdecorators are
> > strictly more powerful than metaclasses, without cumbersome
> > convertions between metaclass mechanisms and decorator mechanisms.
> The assertion that classdecorators are strictly more powerful than
> custom metaclasses is simply false.  How would you design
> classdecorator XXX so that
> @XXX
> class Foo: ...
> allows 'print Foo' to emit 'this is beautiful class Foo', for example?
>  the str(Foo) implicit in print calls type(Foo).__str__(Foo), so you
> do need a custom type(Foo) -- which is all that is meant by "a custom
> metaclass"... a custom type whose instances are classes, that's all.

I would argue that this is not such a useful feature, as in that case
you can simply use a factory object instead of a class.  If this
feature remains, that's fine, but the fact it allows for a weak form
of "decoration" of classes should not kill the concept of class
The only reason of using metaclasses rather than factory objects, in
my experience, was that references to class objects are considered
different than references to factories (by pickle and deepcopy, and
maybe others) and that can be a useful feature. This feature can be
implemented in more readable means though.

> > C. Interesting uses of classdecorators are allowing super-calling
> > without redundantly specifying the name of your class, or your
> > superclass.
> Can you give an example?

class MyClass(object):
     def my_method(self, supcaller, x, y, z):
         result = supcaller.my_method(x, y, z)

Could be nice to remove the need for decorating the class, and only
decorating the methods, but the method decorators get a function
object, not a method object, so its more difficult (perhaps portably
impossible?) to do this.

Note that "__metaclass__ = superclasscaller" could also work, but then
combining "anotherclassdecorator" would require a lot more code at
worst, or a complex mechanism to combine metaclasses via multiple
inheritence at best.

> > D. Python seems to be incrementally adding power to the core language
> > with these features, which is great, but it also causes significant
> > overlapping of language features, which I believe is something to
> > avoid when possible.  If metaclasses are replaced with class
> > decorators, then suddenly inheritence becomes a redundant feature.
> And how do you customize what "print Foo" emits, as above?

As I said, "Foo" can be a factory object rather than a class object.

> > E. If inheritence is a redundant feature, it can be removed and an
> > "inherit" class decorator can be used.  This could also reduce all the
> > __mro__ clutter from the language along with other complexities, into
> > alternate implementations of the inherit classdecorator.
> How do you propose to get exactly the same effects as inheritance
> (affect every attribute lookup on a class and its instances) without
> inheritance?  Essentially, inheritance is automated delegation
> obtained by having getattr(foo, 'bar') look through a chain of objects
> (essentially the __mro__) until an attribute named 'bar' is found in
> one of those objects, plus a few minor but useful side effects, e.g.
> on isinstance and issubclass, and the catching of exceptions in
> try/except statements.  How would any mechanism allowing all of these
> uses NOT be inheritance?

One possibility is to copy the superclass attributes into subclasses.
Another is to allow the class decorator to specify
getattr/setattr/delattr's implementation without modifying the
metaclass [admittedly this is a difficult/problematic solution].
In any case, the inheritence class decorator could specify special
attributes in the class (it can remain compatible with __bases__) for
isinstance/try to work.

I agree that implementing inheritence this way is problematic [I'm
convinced], but don't let that determine the fate of class decorators
in general, which are more useful than metaclasses in many (most?)

More information about the Python-Dev mailing list