solving the metaclass conflict

Phillip J. Eby pje at telecommunity.com
Sun Jun 8 14:24:06 EDT 2003


mis6 at pitt.edu (Michele Simionato) wrote in message news:<2259b0e2.0306080334.329fa409 at posting.google.com>...
> pje at telecommunity.com (Phillip J. Eby) wrote in message news:<25b5433d.0306071443.63b27df6 at posting.google.com>...
> > mis6 at pitt.edu (Michele Simionato) wrote in message news:<2259b0e2.0306070629.7f5fcad7 at posting.google.com> <http://groups.google.com/groups?selm=2259b0e2.0306070629.7f5fcad7%40posting.google.com>...
> > > 
> > * Handle non-type metatype roots (e.g. ExtensionClass and MESS)
> 
> I don't use this kind of stuff and they are not standard,  so it was
> not in the design to support them.

At the time I wrote my metaclass generator, ExtensionClass at least
was in common usage; still is, actually, for Zope 2 users.  My
understanding, however, is that in principle you could still create a
pure-Python root metatype, for experimenting with metatype systems. 
But I'll certainly agree that that's a *very* niche usage.


> >* Remove unneeded metaclasses that are a subclass of another listed
> >metaclass
> 
> Probably, as David says, you mean the reverse: one should remove
> unneeded
> metaclasses that are a superclass of another listed metaclass. Does
> not seem
> so dangerous, but I will fix it.

Yes, that's what I meant.  Getting *that* bit of logic to work
correctly has occasionally made my head spin, especially deciding how
to handle the ordering of the resulting filtered set of bases.



> > >* Reuse all generated metaclasses (You're memoizing on a set of base
> >classes, rather than on the set of unique metaclasses.)
> 
> I am also memoizing the passed metaclasses (i.e. the 'metas' tuple)
> and
> from the tuple of bases classes I get the corresponding metaclasses,
> so
> it is not obvious when _generatemetaclass will fail. Can you give a
> counterexample ? If yes, I will fix it.

class M1(type): pass
class M2(type): pass
class B1(object): __metaclass__ = M1
class B2(object): __metaclass__ = M2
class B3(B2): pass

class C1(B1,B2): __metaclass__ = funcThatMakesMetaclass
class C2(B1,B3): __metaclass__ = funcThatMakesMetaclass

If I understand your code correctly, a new metaclass will be generated
for C2, even though the metaclass generated for C1 would be adequate.


> >It's bloody difficult to write a correct, complete, and efficient
> >metaclass generator, so it's no wonder Guido chose to punt on this in
> >2.2 (and 2.3 for that matter).
> 
> I would say it is difficult, indeed. Maybe not bloody difficult,
> though.

Well, I needed an "industrial strength" version, because it was for an
AOP tool that would process *all* classes in modules run through the
AOP tool.  So it had to work for every possible kind of class.  Also,
because it could be used to generate metaclasses on the fly, I wanted
the memoization to be weak-reference based, so that cached metaclasses
would be discarded when no longer used.  And of course I wanted it to
be fast, because it was used to load generated modules with hundreds
of classes.  So I used memoizing based on the metaclass bases, rather
than on the class bases.

Anyway, *writing* it wasn't the hard part.  Getting it to be *correct*
for all inputs was the hard part.  :)


> I think may recipe is good enough for most users, especially since
> they
> can understand how it works and its limitations (which were indicated
> in the commented lines BTW; I don't think meta-metaclasses are so
> largely used to deserve explicit mention). Anyway, I will fix the
> issue
> with meta-metaclasses if it can be done with few lines of code, and
> the issue with unneeded metaclasses, too. Not sure if I want to fix
> the
> issue with classic classes, tough.

Under Python 2.2, you can't mix a new-style class with 'type' (and not
cause Python to core-dump when you inspect the resulting class!).  So
if you want to create a mixin that can be used with 'type' as well as
other kinds of classes, you *have* to use a classic class.

Anyway...  the punchline to all of this is...  I quickly discovered
that for normal usage (i.e. not via automated tools like my AOP code),
you don't need even as sophisticated of a recipe as what you've
created, let alone what I wrote.  It's just as easy to do:

class C1(B1,B2):
    class __metaclass__(B1.__class__,B2.__class___): pass

While a bit verbose, I find that about 90% of the time when I need to
combine metaclasses, there are only two involved, and then the
inheritance hierarchy stabilizes again.  Perhaps you should mention
this "manual recipe" along with your automatic one, since it's a lot
less than 25 lines if you only need to do it once or twice.




More information about the Python-list mailing list