Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

solving the metaclass conflict

1 view
Skip to first unread message

Michele Simionato

unread,
Jun 7, 2003, 10:29:59 AM6/7/03
to
I have just posted a recipe on the online Cookbook that could be of interest
for metaclasses users.

----------------------------------------------------------------------------

Description:

Any serious user of metaclasses has been bitten at least once by the
infamous metaclass/metatype conflict. Here I give a general recipe
to solve the problem, as well as some theory and some examples.

--> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197


Michele

Phillip J. Eby

unread,
Jun 7, 2003, 6:43:10 PM6/7/03
to
mi...@pitt.edu (Michele Simionato) wrote in message news:<2259b0e2.03060...@posting.google.com>...

FYI, note that your implementation doesn't:

* Handle classic classes in the base class list ('trivial()' should
check for 'is ClassType' as well as 'is type')

* Handle non-type metatype roots (e.g. ExtensionClass and MESS)

* Handle multi-level metaclasses (e.g. metametaclasses)

* Remove unneeded metaclasses that are a subclass of another listed
metaclass

* Reuse all generated metaclasses (You're memoizing on a set of base
classes, rather than on the set of unique metaclasses.)

PEAK's 'peak.util.Meta' module contains a more-complete implementation
of this technique. (See http://peak.telecommunity.com/ for info on
PEAK.)

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).

David Mertz

unread,
Jun 7, 2003, 11:29:24 PM6/7/03
to
|> --> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197
|FYI, note that your implementation doesn't:

While Michele wrote the abovementioned module, I helped him enhance his
initial version, so I've thought about it some. I'm not sure I
understand all of Eby's points:

|* Handle classic classes in the base class list ('trivial()' should
|check for 'is ClassType' as well as 'is type')

True. It might make sense to add this--although Michele was explicit in
our discussions about wanting to be strictly "new style"... but since
the fix doesn't actually break anything, there's no real reason not to
do this, even we don't -recommend- including old style classes.

|* Handle non-type metatype roots (e.g. ExtensionClass and MESS)

Hmmm... I never thought about this, and don't really know what the
issues are. Where does this come up?

|* Handle multi-level metaclasses (e.g. metametaclasses)

This I don't get at all. I've never used a metametaclass (and doubt I
will). But even assuming I did, the type() of a metaclass resolves to
such a metametaclass, and the proposed code should work fine.

Is something else intended here?

|* Remove unneeded metaclasses that are a subclass of another listed
|metaclass

I think Eby means the reverse of what he actually writes. It's not the
subclass but the superclass that might get removed, methinks.

E.g.:

from noconflict import _generatemetaclass
class A(type): pass
class B(A): pass
class C(type): pass
M = _generatemetaclass((),(B,A,C),0)
print M #-> <class 'noconflict._BAC'>

Now admittedly, the result <class 'noconflict._BC'> should be just as
good. But I don't think anything really behaves differently as is.

Of course:

M = _generatemetaclass((),(A,B,C),0)
#-> TypeError: MRO conflict among bases B, C, A

Which is a bad thing. Is that the real concern?

|* Reuse all generated metaclasses (You're memoizing on a set of base
|classes, rather than on the set of unique metaclasses.)

This I don't get either. What's identifies a unique metaclass apart
from an (ordered) collection of base metaclasses? The name of the
metaclass is mechanically generated from the tuple metabases, so that's
the same too.

Can you show an example where something isn't memoized that should be?

|PEAK's 'peak.util.Meta' module contains a more-complete implementation
|of this technique. (See http://peak.telecommunity.com/ for info on
|PEAK.)

I plan on including something like this in gnosis.magic (which is public
domain). May I take (and modify) peak.util.Meta for this purpose if I
decide I want to? I did not notice a prominent notice of its
copyright/license terms on the web site.

Yours, David...

--
Keeping medicines from the bloodstreams of the sick; food from the bellies
of the hungry; books from the hands of the uneducated; technology from the
underdeveloped; and putting advocates of freedom in prisons. Intellectual
property is to the 21st century what the slave trade was to the 16th.

Michele Simionato

unread,
Jun 8, 2003, 7:34:12 AM6/8/03
to
p...@telecommunity.com (Phillip J. Eby) wrote in message news:<25b5433d.03060...@posting.google.com>...
> mi...@pitt.edu (Michele Simionato) wrote in message news:<2259b0e2.03060...@posting.google.com> <http://groups.google.com/groups?selm=2259b0e2.0306070629.7f5fcad7%40posting.google.com>...

> > I have just posted a recipe on the online Cookbook that could be of interest
> > for metaclasses users.
> >
> > ----------------------------------------------------------------------------
> >
> > Description:
> >
> > Any serious user of metaclasses has been bitten at least once by the
> > infamous metaclass/metatype conflict. Here I give a general recipe
^^^^^^
Here I have exaggerated a bit. I am aware my recipe has limitations:
that's why I call it a recipe! Notice that it fits in something like
25 lines,
skipping comments. It is general enough for that size.

>FYI, note that your implementation doesn't:

>* Handle classic classes in the base class list ('trivial()' should
>check for 'is ClassType' as well as 'is type')

This is on design:

1) I don't want people to use classic classes;
2) They will go away at some point;
3) They can be trivially converted to new-style classes with
a __metaclass__=type line at the beginning of the module.

Moreover, as you say, if one really wants it, it is trivial to fix it.

> * 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.

> * Handle multi-level metaclasses (e.g. metametaclasses)

True: look at the source code line

# create new metaclass,shift possible conflict to meta-metaclasses
return type(metaname,metabases,{})

The comments explicitly says that I don't want to fiddle with the
meta-metaclass
conflict. This could be done with a recursive version (something like
substituting 'type' here with 'clsfactory()(metaname,metabases,{})')
but I wanted to keep the recipe SIMPLE, I am not designing an
implementation for usage in core Python (even if I think somebody
should do that).

>* 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.

>* 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.

> PEAK's 'peak.util.Meta' module contains a more-complete implementation
> of this technique. (See <http://peak.telecommunity.com/> for info on
> PEAK.)

I will look at it.

>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.
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.

Thanks for the feedback, it is what I asked for.

Michele

Michele Simionato

unread,
Jun 8, 2003, 7:56:06 AM6/8/03
to
me...@gnosis.cx (David Mertz) wrote in message news:<mailman.1055044454...@python.org>...

> |> --> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197
> |FYI, note that your implementation doesn't:
>
> While Michele wrote the abovementioned module, I helped him enhance his
> initial version, so I've thought about it some.

David, your help was greatly appreciated and you are aknowledged in the
recipe!

> |* Handle multi-level metaclasses (e.g. metametaclasses)
>
> This I don't get at all. I've never used a metametaclass (and doubt I
> will). But even assuming I did, the type() of a metaclass resolves to
> such a metametaclass, and the proposed code should work fine.
>
> Is something else intended here?

Yes, when I wrote the first version of the code, a couple of months ago,
I also found a couple of examples involving meta-metaclasses where
_generatemetaclass fails; I don't find them now, but as soon as I do, I
will post them.

<snip>

> Of course:
>
> M = _generatemetaclass((),(A,B,C),0)
> #-> TypeError: MRO conflict among bases B, C, A
>
> Which is a bad thing. Is that the real concern?

Of course not, since this is an issue with the C3 method resolution
order (http://www.python.org/2.3/mro.html) which in principle has nothing
to do with metaclasses. It is correct to have an error in this case, since
you are putting a less specific class before the more specialized class.
This is also the reason why

class C(object,type): pass

gives a MRO conflict (under Python 2.3). I am sure you know already that,
it was for the sake of c.l.p. readers ;)

Cheers,


Michele

delphiro

unread,
Jun 8, 2003, 7:48:51 AM6/8/03
to
Hi there,

I just tried to start my application on my new linux os (after a severe NVidia install crash :-| ) and everything went fine until I tried to read in a 'pickled' object. The error stated that I was trying to access a 'non-object'.

Does this have anything to do with the change of platform? (Until now I mainly used WinNT)

[mycode]
#part of 'geometry' class
def WriteToFile( self, filename ):
try:
f = open( filename, 'w' )
pickle.dump( self, f )
except:
#TODO: implement
pass

#global function
def CreateGeometryFromFile( filename ):
try:
f = open( filename, 'r' )
return pickle.load(f)
except:
pass
[/mycode]

Regards,
Rob

[complete code]
http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/pyciv/pyciv/unstable/geometry.py?rev=1.1.1.1&content-type=text/vnd.viewcvs-markup

Michele Simionato

unread,
Jun 8, 2003, 12:24:44 PM6/8/03
to
p...@telecommunity.com (Phillip J. Eby) wrote in message news:<25b5433d.03060...@posting.google.com>...
> mi...@pitt.edu (Michele Simionato) wrote in message news:<2259b0e2.03060...@posting.google.com>...
> > I have just posted a recipe on the online Cookbook that could be of interest
> > for metaclasses users.
> >
> > ----------------------------------------------------------------------------
> >
> > Description:
> >
> > Any serious user of metaclasses has been bitten at least once by the
> > infamous metaclass/metatype conflict. Here I give a general recipe
> > to solve the problem, as well as some theory and some examples.
> >
> > --> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197
> >

I think I have fixed the meta-metaclass conflict and the other issue with
unneeded metaclasses, without adding additional lines. Here is the last
version of my class factory:

# noconflict.py

def memoize(f):
"""This closure remembers all f invocations"""
argskw,result = [],[]
def _(*args,**kw):
akw=args,kw
try: # returns a previously stored result
return result[argskw.index(akw)]
except ValueError: # there is no previously stored result
argskw.append(akw) # update argskw
result.append(f(*args,**kw)) # update result
return result[-1] # return the new result
_.argskw=argskw #makes the argskw list accessible outside
_.result=result #makes the result list accessible outside
return _

def clsfactory(*metas,**options):
"""Class factory avoiding metatype conflicts. The invocation syntax is
clsfactory(M1,M2,..,priority=1)(name,bases,dic). If the base classes have
metaclasses conflicting within themselves or with the given metaclasses,
it automatically generates a compatible metaclass and instantiate it.
If priority is True, the given metaclasses have priority over the
bases' metaclasses"""

priority=options.get('priority',False) # default, no priority
trivial=lambda m: sum([issubclass(M,m) for M in metas],m is type)
# hackish!! m is trivial if it is 'type' or, in the case explicit
# metaclasses are given, if it is a superclass of at least one of them

def generatemetaclass(bases,metas,priority):
metabases=tuple([mb for mb in map(type,bases) if not trivial(mb)])
metabases=(metabases+metas, metas+metabases)[priority]
metaname="_"+''.join([m.__name__ for m in metabases])
if not metabases: # trivial metabase
return type
elif len(metabases)==1: # single metabase
return metabases[0]
else: # multiple metabases
return clsfactory()(metaname,metabases,{})
generatemetaclass=memoize(generatemetaclass)

return lambda n,b,d: generatemetaclass(b,metas,priority)(n,b,d)

Notice that I am not going to change the Cookbook recipe yet, since I haven't
tested very well this new version. However, it seems to be working better
than the previous one:

# solving the meta-metaclass conflict
class MM1(type): pass
class MM2(type): pass
class M1(type):
__metaclass__=MM1
class M2(type):
__metaclass__=MM2
class A: __metaclass__=M1
class B: __metaclass__=M2
class C(A,B):
__metaclass__=clsfactory()
print C,type(C),type(type(C))
#=> <class '__main__.C'> <class '__main__._M1M2'> <class '__main__._MM1MM2'>

# skipping unneeded metaclasses
class M(type): pass
class M1(M): pass

class B: __metaclass__=M
class C(B):
__metaclass__=clsfactory(M1)

print C,type(C),type(type(C))
#=> <class '__main__.C'> <class '__main__.M1'> <type 'type'>

If somebody found a case where the new version fails due to some
bug (which is quite possible ;-), please let me know.

Notice that (on purpose) I am not checking against David's example

class A(type): pass
class B(A): pass
class C(type): pass
M = _generatemetaclass((),(B,A,C),0)
print M #-> <class 'noconflict._BAC'>

since in my view only bases classes should be checked. In other words,
I am assuming the user is not perverse and s/he is passing non mutually
redundant metaclasses (whereas the metaclasses corresponding to the base
classes can be redundant). I could improve this later.


Michele

Michele Simionato

unread,
Jun 8, 2003, 1:20:43 PM6/8/03
to
Uhmm ... I think I will refactor further that last snipped, maybe putting
everything in a class. As it is, probably generatemetaclass does not
store the metaclasses correctly. Anyway, the idea is clear and solving
the metaclass conflict at all orders does not seem so difficult ...

Michele

Carl Banks

unread,
Jun 8, 2003, 2:14:23 PM6/8/03
to
me...@gnosis.cx (David Mertz) wrote in message news:<mailman.1055044454...@python.org>...
> |* Handle multi-level metaclasses (e.g. metametaclasses)
>
> This I don't get at all. I've never used a metametaclass (and doubt I
> will). But even assuming I did, the type() of a metaclass resolves to
> such a metametaclass, and the proposed code should work fine.
>
> Is something else intended here?

Just guessing. A has metaclass X which has metaclass P. B has
metaclass Y which has metaclass Q. You want to create class C(A,B).
Your recipe tries to create class Z(X,Y) to serve as the metaclass of
C, but it can't do that because X and Y have different metaclasses
themselves.


--
CARL BANKS

Phillip J. Eby

unread,
Jun 8, 2003, 2:24:06 PM6/8/03
to
mi...@pitt.edu (Michele Simionato) wrote in message news:<2259b0e2.03060...@posting.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.

Phillip J. Eby

unread,
Jun 8, 2003, 2:48:04 PM6/8/03
to
me...@gnosis.cx (David Mertz) wrote in message news:<mailman.1055044454...@python.org>...
> |> --> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204197
> |FYI, note that your implementation doesn't:
>
>
> |* Handle non-type metatype roots (e.g. ExtensionClass and MESS)
>
> Hmmm... I never thought about this, and don't really know what the
> issues are. Where does this come up?

It's related to the multi-level metaclass issue. See below.


> > |* Handle multi-level metaclasses (e.g. metametaclasses)
>
> This I don't get at all. I've never used a metametaclass (and doubt I
> will).

'type' is a metametaclass. It's also a metametameta...class.
ExtensionClass is similar. A "root metatype" is a metaclass that is
its own class.

> But even assuming I did, the type() of a metaclass resolves to
> such a metametaclass, and the proposed code should work fine.
>
> Is something else intended here?

class MetaMeta1(type): pass
class MetaMeta2(type): pass

class Meta1(type): __metaclass__ = MetaMeta1
class Meta2(type): __metaclass__ = MetaMeta2

class Base1: __metaclass__ = Meta1
class Base2: __metaclass__ = Meta2

class C(Base1,Base2): # conflict at metameta level

The conflict can be resolved, because the MetaMetas share the same
root metatype: type. You can resolve any conflict as long as all
meta-to-the-Nth classes eventually merge into the same root metatype.

Let's say, though, that MetaMeta2 was a subclass of ExtensionClass:

>>> type(type)
<type 'type'>

>>> type(type(type))
<type 'type'>

>>> type(ExtensionClass)
<ExtensionClass 'ExtensionClass'>

>>> type(type(ExtensionClass))
<ExtensionClass 'ExtensionClass'>

Trying to resolve the metatype conflict here leads to infinite
recursion, and a confused user of the metaclass generation code. I
wrote code to catch this (i.e. that taking the list of metaclasses of
a list of bases results in the same list of types) so that a
meaningful error message could be given.

Note, by the way, that if 'ClassType' is removed from the list of
metaclasses, then classic classes do not conflict with either
ExtensionClass or type, so a root metatype can be resolved.



> |* Remove unneeded metaclasses that are a subclass of another listed
> |metaclass
>
> I think Eby means the reverse of what he actually writes. It's not the
> subclass but the superclass that might get removed, methinks.

Yes, typo, sorry. I meant that *have* a subclass *as* another listed
metaclass.


> Now admittedly, the result <class 'noconflict._BC'> should be just as
> good. But I don't think anything really behaves differently as is.
>
> Of course:
>
> M = _generatemetaclass((),(A,B,C),0)
> #-> TypeError: MRO conflict among bases B, C, A
>
> Which is a bad thing. Is that the real concern?

That's one part of it, certainly. But I also prefer algorithms like
this to be as visibly deterministic as possible. 2.2's MRO algorithm
is hard enough to predict, without the metaclass generator throwing up
additional obstacles to clarity.


> > |* Reuse all generated metaclasses (You're memoizing on a set of base
> |classes, rather than on the set of unique metaclasses.)
>
> This I don't get either. What's identifies a unique metaclass apart
> from an (ordered) collection of base metaclasses?

The memoization in Michele's recipe is on the base *classes*, not the
classes of the base classes, at least if I understood it correctly.


> |PEAK's 'peak.util.Meta' module contains a more-complete implementation
> |of this technique. (See http://peak.telecommunity.com/ for info on
> |PEAK.)
>
> I plan on including something like this in gnosis.magic (which is public
> domain). May I take (and modify) peak.util.Meta for this purpose if I
> decide I want to? I did not notice a prominent notice of its
> copyright/license terms on the web site.

It's available under the PSF license or the ZPL, as you choose.
Neither is compatible with "public domain", however, if I understand
correctly.

As I mentioned, though, when I first wrote this, I thought it would be
useful outside of the AOP tool it was written for. And then when I
actually got to using metaclasses, I found that it was easy enough to
create a new metaclass by hand, and not worth the trouble of even
importing a function to generate one automatically! I think perhaps
Guido's decision not to generate the metaclass automatically was a
wise one in more ways than one.

Jp Calderone

unread,
Jun 8, 2003, 2:37:35 PM6/8/03
to
On Sun, Jun 08, 2003 at 01:48:51PM +0200, delphiro wrote:
> Hi there,
>
> I just tried to start my application on my new linux os (after a severe
> NVidia install crash :-| ) and everything went fine until I tried to read
> in a 'pickled' object. The error stated that I was trying to access a
> 'non-object'.
>
> Does this have anything to do with the change of platform? (Until now I
> mainly used WinNT)

It is good to include the exact text of your error and the entire
traceback that goes with it. What is *best* is to include just enough
source code (and no more!) so that anyone else can reproduce your error.

From what you've posted, I might guess that you are having newline
problems brought on from writing binary pickles to Windows text files, then
attempted to load them on a platform with no concept of a "text file", but
it is only a guess.

Jp

--
Seduced, shaggy Samson snored.
She scissored short. Sorely shorn,
Soon shackled slave, Samson sighed,
Silently scheming,
Sightlessly seeking
Some savage, spectacular suicide.
-- Stanislaw Lem, "Cyberiad"

delphiro

unread,
Jun 8, 2003, 2:15:35 PM6/8/03
to
Does anyone know if it is possible to add the nice plot windows of the SciPy package in your own (wxWindows based) application so I can show and destroy these windows myself?

Regards,
Rob

Michele Simionato

unread,
Jun 8, 2003, 6:37:25 PM6/8/03
to
p...@telecommunity.com (Phillip J. Eby) wrote in message news:<25b5433d.03060...@posting.google.com>...>
> 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.

Point taken. I wanted something automatic for an AOP-like tool, but it is
true that in most case you don't need it.

Here is my last version:

def memoize(f):
"""This closure stores all f invocations in a dictionary; notice that
f cannot have keywords arguments or dictionaries as arguments."""
dic={} # internal dictionary
wrapped_f=lambda *args: dic.setdefault(args,f(*args))
wrapped_f.dic=dic # make dic available outside
return wrapped_f

def _generatemetaclass(bases,metas,priority):


trivial=lambda m: sum([issubclass(M,m) for M in metas],m is type)
# hackish!! m is trivial if it is 'type' or, in the case explicit
# metaclasses are given, if it is a superclass of at least one of them

metabases=tuple([mb for mb in map(type,bases) if not trivial(mb)])
metabases=(metabases+metas, metas+metabases)[priority]
metaname="_"+''.join([m.__name__ for m in metabases])
if not metabases: # trivial metabase
return type
elif len(metabases)==1: # single metabase
return metabases[0]
else: # multiple metabases
return clsfactory()(metaname,metabases,{})

_generatemetaclass=memoize(_generatemetaclass)

def clsfactory(*metas,**options):
"""Class factory avoiding metatype conflicts. The invocation syntax is
clsfactory(M1,M2,..,priority=1)(name,bases,dic). If the base classes have
metaclasses conflicting within themselves or with the given metaclasses,
it automatically generates a compatible metaclass and instantiate it.
If priority is True, the given metaclasses have priority over the
bases' metaclasses"""

priority=options.get('priority',False) # default, no priority

return lambda n,b,d: _generatemetaclass(b,metas,priority)(n,b,d)

# solving the meta-metaclass conflict
class MM1(type): pass
class MM2(type): pass
class M1(type):
__metaclass__=MM1
class M2(type):
__metaclass__=MM2
class A: __metaclass__=M1
class B: __metaclass__=M2
class C(A,B):
__metaclass__=clsfactory()
print C,type(C),type(type(C))
#=> <class '__main__.C'> <class '__main__._M1M2'> <class '__main__._MM1MM2'>

from oopp import pretty
print pretty(_generatemetaclass.dic)

#print _generatemetaclass.result
#print _generatemetaclass.argskw

class D(A,B):
__metaclass__=clsfactory()


print type(D) is type(C)

David Mertz

unread,
Jun 8, 2003, 6:02:23 PM6/8/03
to
mi...@pitt.edu (Michele Simionato) wrote previously:

|> Of course:
|> M = _generatemetaclass((),(A,B,C),0)
|> #-> TypeError: MRO conflict among bases B, C, A
|> Which is a bad thing. Is that the real concern?

|Of course not, since this is an issue with the C3 method resolution
|order (http://www.python.org/2.3/mro.html) which in principle has
|nothing to do with metaclasses.

Yes and no. I know that MRO conflicts don't have anything to do with
metaclasses per se. But if a version of _generatemetaclass() happened
to remove all the extraneous superclasses from the metabases tuple, that
would make many (all?) MRO conflicts do away.

I think removing those extra superclasses would be a good thing. But I
don't feel strongly; and there might be some reason I have overlooked
why it wouldn't be so good.

|class C(object,type): pass
|gives a MRO conflict (under Python 2.3).

Hmmm... actually, the question never came to my mind. I'm not sure what
my first guess would have been--especially since, as you hint, Python
2.2 behaves differently. Just a little more for readers:

class C(type, object): pass

Is perfectly happy under both 2.2 and 2.3. Again, obvious to Michele,
but probably not to some other readers.

Yours, David...

--
mertz@ _/_/_/_/_/_/_/ THIS MESSAGE WAS BROUGHT TO YOU BY:_/_/_/_/ v i
gnosis _/_/ Postmodern Enterprises _/_/ s r
.cx _/_/ MAKERS OF CHAOS.... _/_/ i u
_/_/_/_/_/ LOOK FOR IT IN A NEIGHBORHOOD NEAR YOU_/_/_/_/_/ g s


Michele Simionato

unread,
Jun 9, 2003, 9:44:40 AM6/9/03
to
me...@gnosis.cx (David Mertz) wrote in message news:<mailman.1055112100...@python.org>...

> mi...@pitt.edu (Michele Simionato) wrote previously:
> |> Of course:
> |> M = _generatemetaclass((),(A,B,C),0)
> |> #-> TypeError: MRO conflict among bases B, C, A
> |> Which is a bad thing. Is that the real concern?
>
> |Of course not, since this is an issue with the C3 method resolution
> |order (http://www.python.org/2.3/mro.html) which in principle has
> |nothing to do with metaclasses.
>
> Yes and no. I know that MRO conflicts don't have anything to do with
> metaclasses per se. But if a version of _generatemetaclass() happened
> to remove all the extraneous superclasses from the metabases tuple, that
> would make many (all?) MRO conflicts do away.
>

Does not smell good to me... MRO conflicts are good, they forbid the
programmer to write ambiguos hierarchies.

> I think removing those extra superclasses would be a good thing. But I
> don't feel strongly; and there might be some reason I have overlooked
> why it wouldn't be so good.
>
> |class C(object,type): pass
> |gives a MRO conflict (under Python 2.3).
>
> Hmmm... actually, the question never came to my mind. I'm not sure what
> my first guess would have been--especially since, as you hint, Python
> 2.2 behaves differently. Just a little more for readers:
>
> class C(type, object): pass
>
> Is perfectly happy under both 2.2 and 2.3. Again, obvious to Michele,
> but probably not to some other readers.
>

Not obvious at all! I was bitten by this, even *after* having written
http://www.python.org/2.3/mro.html and having explained there why
right triangular diagrams like

B
| \
| B1
| /
C

are bad. The C3 MRO is unfortunately not intuitive, even having experience
with it. But I don't know of any other better alternative.


Michele

Michele Simionato

unread,
Jun 9, 2003, 10:08:07 AM6/9/03
to
p...@telecommunity.com (Phillip J. Eby) wrote in message news:<25b5433d.03060...@posting.google.com>...
> mi...@pitt.edu (Michele Simionato) wrote in message news:<2259b0e2.03060...@posting.google.com>...

> 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.

You are indeed correct and I have fixed this issue in the last version in
the cookbook, which fixes all the problems you pointed out until now (and
it is even shorter, under the 20 lines of code!).
Of course, there could be new ones, and I welcome your criticism ;)

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

I couldn't agree more!



> 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.

Really ? I did this kind of games without problems ...
In Python 2.2.0 (in 2.3 too) the following works:

class N(object): pass # new style
class M(N,type): pass # type
help(M) # this inspects the metaclass

Can you give my an example of core-dump, just for curiosity sake?

Thanks again for the feedback,

Michele

Michele Simionato

unread,
Jun 9, 2003, 10:12:40 AM6/9/03
to
p...@telecommunity.com (Phillip J. Eby) wrote in message news:<25b5433d.03060...@posting.google.com>...

> 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.

I misinterpreted you, you are saying that the *class* cannot be
inspected, not the metaclass. You are indeed right:

class N(object): pass
class M(N,type): pass
class C: __metaclass__=M
help(C) # core dump under Python 2.2

Fortunately, this is not the case for Python 2.3, therefore I don't
see the reason to support explicitely old style classes.

Cheers,

Michele

0 new messages