ahuigo
ahuigo

Reputation: 3331

Why does using super() in a __call__ method on a metaclass raise TypeError?

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

Answers (3)

Martijn Pieters
Martijn Pieters

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

MSeifert
MSeifert

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

poke
poke

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

Related Questions