Strange infinite recursion in metaclass's __new__

J

Jp Calderone

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?

Jp
 
A

anton muhin

Jp said:
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?

Jp

What's strange?

object isn't immediate parent of Foo. Therefore, you actually don't
alter bases list :)

anton.
 
A

Alex Martelli

Jp said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top