2014-09-19 47 views
33

为什么我们必须使用__getitem__而不是通常的操作员访问?为什么我们在通过super调用时不得不使用__dunder__方法而不是运算符?

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

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

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

标化只是一个例子,我有__getattr____init__同样的问题,等等。

docs试图解释为什么,但我不明白。

+0

http://stackoverflow.com/questions/12047847/super-object-not-calling-getattr – 2014-09-19 01:15:09

+0

也有帮助:https://docs.python.org/3/reference/datamodel.html#special-method-查找 – wim 2015-06-10 03:51:20

回答

18

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 。

当涉及到像再版(OBJ)的功能,我认为我们要 超级对象识别本身,而不是转发 调用目标对象的__repr __()方法。

的说法似乎是,如果__dunder__方法代理,那么无论是__repr__代理或它们之间的不一致性。因此,可能不希望代理这样的方法,以免它们离程序员相当不可思议的山谷太近。

3

Dunder方法必须在类上定义,而不是实例。 super()需要实现每个魔术方法才能完成这项工作。当你只需要告诉用户手工写出dunder方法时,不必写出所有的代码并保持它与语言定义的最新一致(例如,在3.5中引入了矩阵乘法创建了三种新的dunder方法)。这使用普通的方法查找,可以很容易地模拟。

+2

FWIW,这似乎是我的(现在自我删除)答案的子集。赏金说“目前的答案不适合我”,所以这一个可能也不会。 – Veedrac 2015-06-05 20:04:49

3

你问什么可以做到,而且很容易。例如:

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) 

所以超级用magic方法工作的原因并不是因为它不可能。原因必须在别处。原因之一是这样做会违反等于(==)的合同。除此之外,这是等同的,是对称的。这意味着如果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 

的另一个原因是,一旦超做搜索它给对象的MRO,它就必须给自己一个机会,以提供所要求的属性 - 超级对象仍然在自己的权利的对象 - 我们应该能够测试与其他对象的平等,要求字符串表示,并反思超级正在使用的对象和类。如果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__属性(该属性确实存在)。我们得到该方法并调用它,从而再次启动整个过程。如果我们没有在超级方面编写__iadd__方法并使用super().__iadd__(other),那么这绝不会发生。相反,我们会收到关于没有属性__iadd__的超级对象的错误消息。有点神秘,但不如无限堆栈轨迹。

所以super不能用magic方法工作的原因是它创造了比解决问题更多的问题。

相关问题