Reputation: 3331
I'm using Python 3, and I found that I can't use super()
within __call__
of metaclass.
Why in the code below does super()
raise a TypeError: 'ListMetaclass' object is not iterable
exception? Why does it works well if I remove the __call__
method from the metaclass?
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
new_cls = type.__new__(cls, name, bases, attrs)
return new_cls
def __call__(cls, *args, **kw):
### why the 'super()' here raises TypeError: 'ListMetaclass' object is not iterable
return super().__call__(cls, *args, **kw)
return super(__class__, cls).__call__(cls, *args, **kw)
return super(__class__, cls.__class__).__call__(cls, *args, **kw)
class MyList(list, metaclass=ListMetaclass):
a=1
def bar(self):
print('test');
L=MyList()
L.add(1)
print('Print MyList :', L)
Upvotes: 3
Views: 1594
Reputation: 1122222
You should not pass in cls
to super().__call__()
; super()
takes care of binding for you and so cls
is already passed in automatically.
You may have gotten confused by the super().__new__(cls, ...)
call in __new__
; that's because __new__
is the exception here, see the object.__new__
documentation:
Called to create a new instance of class
cls. __new__()
is a static method (special-cased so you need not declare it as such) that takes the class of which an instance was requested as its first argument.
Removing cls
from the super().__call__(...)
expression works:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
new_cls = type.__new__(cls, name, bases, attrs)
return new_cls;
def __call__(cls, *args, **kw):
return super().__call__(*args, **kw)
By passing in cls
, you are effectively executing list(cls)
, telling list()
to convert cls
to values in the new list; that required cls
to be an iterable.
When you remove the __call__
method on your metaclass, the default type.__call__
method is used when you call MyClass()
, which just receives the normal arguments (none in your example) and returns a new instance.
Upvotes: 4
Reputation: 152667
I can see where this will be confusing because you have to pass cls
to super().__new__
but you mustn't pass it to super().__call__
.
That's because __call__
is a normal method but __new__
is special. It's behaving like a staticmethod
:
object.__new__(cls[, ...])
Called to create a new instance of class
cls
.__new__()
is a static method (special-cased so you need not declare it as such) [...]
As staticmethod it needs all arguments while you don't need to pass the first argument to normal methods (it's already bound).
So just change it to:
return super().__call__(*args, **kw)
inside __call__
.
Upvotes: 2
Reputation: 387745
Accessing a method of the super class using super().method
will already bind the method to the current instance. So the self
argument for methods will already be set automatically, just like when you call self.method()
.
So when you then pass the current instance (in this case, the cls
type) to the method, you are actually passing it a second time.
super().__call__(cls, *args, **kw)
So this will end up making a call __call__(cls, cls, *args, **kw)
.
When this is then interpreted by your __call__
method, the arguments will then be matched to the following definition:
def __call__(cls, *args, **kw):
So the first cls
is properly matched, but the second cls
is interpreted to be the variable parameter list *args
. And here’s where your exception comes from: cls
is being passed where an iterable is expected, but cls
, the class ListMetaclass
, is not iterable.
So the fix here is simply to remove that additional cls
: super().method()
will already include it automatically due to method binding.
Upvotes: 1