Reputation: 61
The following code produces an error:
class FooMeta(type):
def __new__(mcl, classname, bases, classdict):
return type.__new__(mcl, classname, bases, dict())
def __init__(cls, name, bases, classdict):
super(FooMeta, cls).__init__(name, bases, classdict)
d = dict()
for key, item in classdict.items():
if key.startswith("_"):
setattr(cls, key, item)
else:
d[key] = item
setattr(cls, '_items', d)
class Foo(object):
__metaclass__ = FooMeta
class Passing(Foo):
def __init__(self, *args, **kw):
pass
class Failing(Foo):
def __new__(cls, *args, **kw):
return super(Failing, cls).__new__(cls)
def __init__(self, *args, **kw):
pass
fail = Failing()
The error is
Traceback (most recent call last):
File ".../FooMeta,py", line 30, in <module>
fail = Failing()
TypeError: unbound method __new__() must be called with Failing instance as first argument (got FooMeta instance instead)
There appears to be some difference in the way __new__
is called when called from a metaclass.
1) Anyone know why this is happening? 2) Is there some way to fix it?
Upvotes: 6
Views: 369
Reputation: 879411
If you allow the attributes to be set as is normally done:
class FooMeta(type):
def __new__(mcl, classname, bases, classdict):
return type.__new__(mcl, classname, bases, classdict)
and do not fiddle with them in __init__
, you see that Failing.__new__
is normally a staticmethod:
class FooMeta(type):
def __new__(mcl, classname, bases, classdict):
return type.__new__(mcl, classname, bases, classdict)
# return type.__new__(mcl, classname, bases, dict())
def __init__(cls, name, bases, classdict):
super(FooMeta, cls).__init__(name, bases, classdict)
d = dict()
for key, item in classdict.items():
if key.startswith("_"):
# setattr(cls, key, item)
pass
else:
d[key] = item
setattr(cls, '_items', d)
class Foo(object):
__metaclass__ = FooMeta
class Failing(Foo):
def __new__(cls, *args, **kw):
return super(Failing, cls).__new__(cls)
def __init__(self, *args, **kw):
pass
print(Failing.__dict__['__new__'])
foo = Failing()
yields:
<staticmethod object at 0xb774ba94>
But if you run the code you posted with this print statement tacked on the end:
print(Failing.__dict__['__new__'])
foo = Failing()
you see that __new__
has been changed to a function:
<function __new__ at 0xb747372c>
You can fix this by allowing all the attributes in classdict to be set as would normally be done, and then deleting those attributes which do not start with _
:
class FooMeta(type):
def __new__(mcl, classname, bases, classdict):
return type.__new__(mcl, classname, bases, classdict)
def __init__(cls, name, bases, classdict):
super(FooMeta, cls).__init__(name, bases, classdict)
d = dict()
for key, item in classdict.items():
if not key.startswith("_"):
delattr(cls, key)
d[key] = item
setattr(cls, '_items', d)
class Foo(object):
__metaclass__ = FooMeta
class Passing(Foo):
def __init__(self, *args, **kw):
pass
class Failing(Foo):
def __new__(cls, *args, **kw):
return super(Failing, cls).__new__(cls)
def __init__(self, *args, **kw):
pass
print(Failing.__dict__['__new__'])
foo = Failing()
Upvotes: 3