简体   繁体   中英

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)

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__() 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.

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__ .

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM