为什么在通过super调用时我们必须使用__dunder__方法而不是运算符?

wim*_*wim 33 python super

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

class MyDict(dict):
    def __getitem__(self, key):
        return super()[key]
Run Code Online (Sandbox Code Playgroud)

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

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

标化只是一个例子,我也有同样的问题__getattr__,__init__等等.

文档试图解释为什么,但我不明白.

Vee*_*rac 20

CPython的错误跟踪器问题805304,"超级实例不支持项目分配", Raymond Hettinger详细解释了感知困难.

这不起作用的原因是由于Python的方法缓存,必须在类上定义这样的方法,而代理方法在运行时找到.

他提供了一个可以提供此功能子集的补丁:

+   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;
+   }
+ 
Run Code Online (Sandbox Code Playgroud)

所以显然是可能的.

不过,他总结道

我一直在想这个可以留下来,只记录超级对象只有在显式属性查找时才能发挥作用.

否则,完全修复它需要为每个直接从插槽表调用函数的地方组合Python,然后在插槽为空时使用属性查找添加后续调用.

当谈到像repr(obj)这样的函数时,我认为我们希望超级对象能够识别自身,而不是将调用转发给目标对象的__repr __()方法.

参数似乎是如果__dunder__方法被代理,则要么代理,要么__repr__它们之间存在不一致.super()因此,可能不想代理这样的方法,以免它太接近程序员相当于一个不可思议的山谷.


Dun*_*nes 5

你所要求的都可以完成,而且很容易。例如:

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)
Run Code Online (Sandbox Code Playgroud)

所以 super 与魔法方法一起工作的原因并不是因为它不可能。原因一定在其他地方。原因之一是这样做会违反 equals ( ) 约定==。除其他标准外,即等于是对称的。这意味着如果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
Run Code Online (Sandbox Code Playgroud)

另一个原因是,一旦 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
Run Code Online (Sandbox Code Playgroud)

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

def __iadd__(self, other):
    return super().__iadd__(other)
Run Code Online (Sandbox Code Playgroud)

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

所以 super 不能与魔法方法一起使用的原因是它产生的问题多于它解决的问题。