Strange infinite recursion in metaclass's __new__

Alex Martelli aleax at aleax.it
Mon Nov 10 20:53:05 CET 2003


Jp Calderone wrote:

>   Due to some bizarre constraints placed on me, I've written the following
> metaclass:
> 
>     import types
> 
>     def remove(t, o):
>         return tuple([e for e in t if t not in o])
> 
>     class BizarreMetaclass(type):
>         def __new__(klass, name, bases, attrs):
>             if name == 'BaseClass':
>                 return type.__new__(klass, name, bases, attrs)
>             return types.ClassType(name, remove(bases, [object]), attrs)
> 
>     class BaseClass(object):
>         __metaclass__ = BizarreMetaclass
> 
>     class Foo(BaseClass):
>         pass
> 
>   The solution is extremely hacky (but I've already tried and discarded
> about a dozen more straightforward attempted solutions).
> 
>   It is also broken, but it is broken in a way that I do not understand.
> 
>   Can anyone explain the unbounded recursion that occurs?

type.ClassType bends over backwards to accomodate classes that do in
fact have a metaclass of 'type'; this is basically to let you code:

class WeirdMix(classicone, object): ...

to obtain a _new-style_ class otherwise equivalent to classicone (save
for whatever additions and overrides you may choose to do in the body
of WeirdMix).

Since you don't remove Baseclass from among the bases before calling
types.ClassType, then it identifies that one base class has a metaclass
of BizarreMetaclass and defers to it -- whence the recursion.  I did try
to cover this in my Europython tutorial on metaclasses, btw -- it IS an
important point, and an often-overlooked one.

Since it does not appear to me that you NEED 'BaseClass' to remain
among the bases of Foo, why don't you remove it, just as you remove
object...?

There's also a bug in your remove function, and the name of the first
arg to __new__ in a metaclass should be mcl (just like it should be
cls for __new__ in a normal class and self for other methods) -- I
proposed that at Europython and Guido liked it;-) -- so, overall:

import types

def remove(t, o):
    return tuple([e for e in t if e not in o])

class BizarreMetaclass(type):
    omit = [object]
    def __new__(mcl, name, bases, attrs):
        if name == 'BaseClass':
            result = type.__new__(mcl, name, bases, attrs)
            mcl.omit.append(result)
            return result
        return types.ClassType(name, remove(bases, mcl.omit), attrs)

class BaseClass(object):
    __metaclass__ = BizarreMetaclass

class Foo(BaseClass):
    pass

f = Foo()
print f, type(f), f.__class__
print isinstance(f, BaseClass)
print issubclass(Foo, BaseClass)

this print, as required and expected:


<__main__.Foo instance at 0x402decac> <type 'instance'> __main__.Foo
False
False


Alex





More information about the Python-list mailing list