【问题标题】:Why does using super() in a __call__ method on a metaclass raise TypeError?为什么在元类的 __call__ 方法中使用 super() 会引发 TypeError?
【发布时间】:2018-03-12 04:32:01
【问题描述】:

我使用的是 Python 3,发现无法在元类的 __call__ 内使用 super()

为什么在下面的代码中super() 会引发TypeError: 'ListMetaclass' object is not iterable 异常?如果我从元类中删除 __call__ 方法,为什么它可以正常工作?

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)

【问题讨论】:

    标签: python python-3.x super metaclass


    【解决方案1】:

    您不应将cls 传递给super().__call__()super() 负责为您绑定,所以 cls已经自动传入。

    您可能对__new__ 中的super().__new__(cls, ...) 调用感到困惑;那是因为__new__ 是这里的例外,请参阅object.__new__ documentation

    调用以创建类 cls. __new__() 的新实例是一个静态方法(特殊情况,因此您无需声明它),它将请求实例的类作为其第一个参数。

    super().__call__(...) 表达式中删除cls 有效:

    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)
    

    通过传入cls,您实际上是在执行list(cls),告诉list()cls 转换为新列表中的值;这要求 cls 是可迭代的。

    当您删除元类上的__call__ 方法时,调用MyClass() 时会使用默认的type.__call__ 方法,它只接收普通参数(在您的示例中没有)并返回一个新实例。

    【讨论】:

      【解决方案2】:

      我知道这会让人感到困惑,因为您必须将 cls 传递给 super().__new__,但您不能将其传递给 super().__call__

      那是因为__call__ 是一种普通的方法,而__new__ 是特殊的。我是behaving like a staticmethod

      object.__new__(cls[, ...])

      调用以创建类cls 的新实例。 __new__() 是一个静态方法(特殊情况,所以你不需要这样声明)[...]

      作为静态方法,它需要所有参数,而您不需要将第一个参数传递给普通方法(它已经绑定)。

      所以把它改成:

      return super().__call__(*args, **kw)
      

      __call__内。

      【讨论】:

        【解决方案3】:

        使用super().method 访问超类的方法已经将方法绑定到当前实例。因此,方法的 self 参数将已经自动设置,就像您调用 self.method() 时一样。

        因此,当您将当前实例(在本例中为 cls 类型)传递给方法时,实际上是第二次传递它。

        super().__call__(cls, *args, **kw)
        

        所以这最终会拨打电话__call__(cls, cls, *args, **kw)

        当您的 __call__ 方法随后对此进行解释时,参数将与以下定义相匹配:

        def __call__(cls, *args, **kw):
        

        所以第一个cls被正确匹配,但是第二个cls被解释为变量参数列表*args。这就是您的异常的来源:cls 被传递到预期可迭代的地方,但 clsListMetaclass 不可迭代。

        所以这里的修复只是简单地删除额外的clssuper().method() 由于方法绑定已经自动包含它。

        【讨论】:

          猜你喜欢
          • 2015-08-31
          • 2018-02-13
          • 2010-09-28
          • 2017-10-02
          • 1970-01-01
          • 2018-11-08
          • 2010-10-04
          • 1970-01-01
          • 2011-10-21
          相关资源
          最近更新 更多