【问题标题】:Why do we have to use the __dunder__ methods instead of operators when calling via super?为什么我们在通过 super 调用时必须使用 __dunder__ 方法而不是运算符?
【发布时间】:2014-11-13 11:53:23
【问题描述】:

为什么我们必须使用__getitem__ 而不是通常的操作员访问权限?

class MyDict(dict):
    def __getitem__(self, key):
        return super()[key]

我们得到TypeError: 'super' object is not subscriptable

相反,我们必须使用super().__getitem__(key),但我从未完全理解为什么 - 究竟是什么阻止了 super 以允许操作员访问的方式实现?

Subscriptable 只是一个例子,我对__getattr____init__ 等也有同样的问题。

docs 试图解释原因,但我不明白。

【问题讨论】:

标签: python super


【解决方案1】:

CPython 的错误跟踪器 issue 805304, "super instances don't support item assignment", 让 Raymond Hettinger 详细解释了感知到的困难。

这不能自动工作的原因是由于 Python 对方法的缓存,必须在类上定义这些方法,而代理方法是在运行时找到的。

他提供了a patch,它将提供此功能的一个子集:

+   if (o->ob_type == &PySuper_Type) {
+       PyObject *result;
+       result = PyObject_CallMethod(o, "__setitem__", "(OO)", key, value);
+       if (result == NULL)
+           return -1;
+       Py_DECREF(result);
+       return 0;
+   }
+ 

显然可能

但是,他得出结论

我一直在想这个可以单独留下来 记录超级物体只对它们施魔法 显式属性查找。

否则,完全修复它需要结合 Python 每个直接从槽表中调用函数的地方, 然后使用属性查找添加后续调用,如果 插槽是空的。

当谈到像 repr(obj) 这样的函数时,我想我们想要 超级对象来识别自己而不是转发 调用目标对象的 __repr__() 方法。

争论似乎是,如果__dunder__ 方法被代理,那么要么__repr__ 被代理,要么它们之间存在不一致。因此,super() 可能不想代理这些方法,以免它太接近程序员的恐怖谷。

【讨论】:

    【解决方案2】:

    您的要求可以轻松完成。例如:

    class dundersuper(super):
        def __add__(self, other):
            # this works, because the __getattribute__ method of super is over-ridden to search 
            # through the given object's mro instead of super's.
            return self.__add__(other)
    
    super = dundersuper
    
    class MyInt(int):
        def __add__(self, other):
            return MyInt(super() + other)
    
    i = MyInt(0)
    assert type(i + 1) is MyInt
    assert i + 1 == MyInt(1)
    

    所以 super 使用魔法方法的原因并不是因为它不可能。原因一定在别处。一个原因是这样做会违反平等合同(==)。也就是说,除其他标准外,平等是对称的。这意味着如果a == b 为真,那么b == a 也必须为真。这让我们陷入了一个棘手的境地,super(self, CurrentClass) == self,但self != super(self, CurrentClass) 例如。

    class dundersuper(super):
        def __eq__(self, other):
            return self.__eq__(other)
    
    super = dundersuper
    
    class A:
        def self_is_other(self, other):
            return super() == other # a.k.a. object.__eq__(self, other) or self is other
        def __eq__(self, other):
            """equal if both of type A"""
            return A is type(self) and A is type(other)
    
    class B:
        def self_is_other(self, other):
            return other == super() # a.k.a object.__eq__(other, super()), ie. False
        def __eq__(self, other):
            return B is type(self) and B is type(other)
    
    assert A() == A()
    a = A()
    assert a.self_is_other(a)
    assert B() == B()
    b = B()
    assert b.self_is_other(b) # assertion fails
    

    另一个原因是,一旦 super 完成搜索,它会获得对象的 mro,然后它必须给自己一个机会来提供请求的属性 - super 对象本身仍然是一个对象 - 我们应该能够测试与其他对象相等,请求字符串表示,并内省 super 正在使用的对象和类。如果 dunder 方法在超级对象上可用,但在可变对象所代表的对象上不可用,这会产生问题。例如:

    class dundersuper(super):
        def __add__(self, other):
            return self.__add__(other)
        def __iadd__(self, other):
            return self.__iadd__(other)
    
    super = dundersuper
    
    class MyDoubleList(list):
        """Working, but clunky example."""
        def __add__(self, other):
            return MyDoubleList(super() + 2 * other)
        def __iadd__(self, other):
            s = super()
            s += 2 * other  # can't assign to the result of a function, so we must assign 
            # the super object to a local variable first
            return s
    
    class MyDoubleTuple(tuple):
        """Broken example -- iadd creates infinite recursion"""
        def __add__(self, other):
            return MyDoubleTuple(super() + 2 * other)
        def __iadd__(self, other):
            s = super()
            s += 2 * other
            return s
    

    对于列表示例,函数__iadd__ 可以更简单地写成

    def __iadd__(self, other):
        return super().__iadd__(other)
    

    对于元组示例,我们陷入了无限递归,这是因为tuple.__iadd__ 不存在。因此,当在超级对象上查找属性__iadd__ 时,会检查实际超级对象的__iadd__ 属性(确实存在)。我们得到那个方法并调用它,这又开始了整个过程。如果我们没有在 super 上编写 __iadd__ 方法并使用 super().__iadd__(other),那么这将永远不会发生。相反,我们会收到关于没有属性__iadd__ 的超级对象的错误消息。有点神秘,但比无限堆栈跟踪要少。

    所以 super 不能使用魔法方法的原因是它产生的问题比它解决的要多。

    【讨论】:

      【解决方案3】:

      Dunder 方法必须定义在类上,而不是实例上。 super() 需要实现每个魔术方法才能使其工作。编写所有代码并使其与语言定义保持同步是不值得的(例如,在 3.5 中引入矩阵乘法创建了三个新的 dunder 方法),当您可以告诉用户手动写出 dunder 方法时。它使用正常的方法查找,可以轻松模拟。

      【讨论】:

      • FWIW,这似乎是我(现在自删除)答案的一个子集。赏金说“目前的答案对我来说没有说服力”,所以这个可能也不会。
      猜你喜欢
      • 2010-10-28
      • 1970-01-01
      • 2014-07-02
      • 1970-01-01
      • 2011-01-22
      • 2016-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多