为 Python 返回 NotImplemented 的特殊方法执行 MRO
Walk MRO for Python special methods returning NotImplemented
我有一个 classes 的层次结构,用于实现特殊方法(例如 __mul__
和 __add__
)并使用多重继承的代数对象。我以某种方式假设 Python (>= 3.5) 将遍历方法解析顺序 (mro) 以找到第一个不 return NotImplemented
的方法。 las,情况似乎并非如此。考虑以下最小示例:
class A():
def __mul__(self, other):
return "A * %s" % other
class B():
def __mul__(self, other):
if isinstance(other, int):
return "B * %s" % other
else:
return NotImplemented
class C(B, A):
pass
class D(B, A):
def __mul__(self, other):
res = B.__mul__(self, other)
if res is NotImplemented:
res = A.__mul__(self, other)
return res
在这段代码中,我实现了 D
所需的行为:
>>> d = D()
>>> d * 1
'B * 1'
>>> d * "x"
'A * x'
然而,我实际上希望 C
的行为与 D
相同,但事实并非如此:
>>> c = C()
>>> c * 1
'B * 1'
>>> c * "x"
Traceback (most recent call last):
File "<ipython-input-23-549ffa5b5ffb>", line 1, in <module>
c * "x"
TypeError: can't multiply sequence by non-int of type 'C'
我明白发生了什么,当然:我只是 returning mro 中第一个匹配方法的结果(我只是希望 NotImplemented
会被处理为特殊值)
我的问题是,是否有任何方法可以避免编写像 D.__mul__
这样的样板代码(对于所有 classes,所有数值特殊方法基本上都是相同的)。我想我可以写一个 class 装饰器或 metaclass 来自动生成所有这些方法,但我希望会有一些更简单的(标准库)方法,或者,有人已经做了一些事情像这样。
Python 在您要求时走上 MRO,这并不意味着继续检查更高。将您的代码更改为使用 super()
的合作继承(将 MRO 移动到下一个 class 的请求),否则 return NotImplemented
它应该可以工作。它完全不需要 C
或 D
来定义 __mul__
,因为它们不会为其功能添加任何内容:
class A():
def __mul__(self, other):
return "A * %s" % other
class B():
def __mul__(self, other):
if isinstance(other, int):
return "B * %s" % other
try:
return super().__mul__(other) # Delegate to next class in MRO
except AttributeError:
return NotImplemented # If no other class to delegate to, NotImplemented
class C(B, A):
pass
class D(B, A):
pass # Look ma, no __mul__!
然后测试:
>>> d = D()
>>> d * 1
'B * 1'
>>> d * 'x'
'A * x'
super()
的神奇之处在于它甚至可以在多重继承场景中工作,在这种情况下,class、B
对 A
一无所知,但会如果 child 碰巧从两者继承,仍然很乐意委托给它(或任何其他可用的 class)。如果没有,我们将像以前一样处理结果 AttributeError
以生成结果 NotImplemented
,所以像这样的东西会按预期工作(它会尝试 str
的 __rmul__
不识别非int
并爆炸):
>>> class E(B): pass
>>> e = E()
>>> e * 1
'B * 1'
>>> e * 'x'
Traceback (most recent call last)
...
TypeError: can't multiply sequence by non-int of type 'E'
我有一个 classes 的层次结构,用于实现特殊方法(例如 __mul__
和 __add__
)并使用多重继承的代数对象。我以某种方式假设 Python (>= 3.5) 将遍历方法解析顺序 (mro) 以找到第一个不 return NotImplemented
的方法。 las,情况似乎并非如此。考虑以下最小示例:
class A():
def __mul__(self, other):
return "A * %s" % other
class B():
def __mul__(self, other):
if isinstance(other, int):
return "B * %s" % other
else:
return NotImplemented
class C(B, A):
pass
class D(B, A):
def __mul__(self, other):
res = B.__mul__(self, other)
if res is NotImplemented:
res = A.__mul__(self, other)
return res
在这段代码中,我实现了 D
所需的行为:
>>> d = D()
>>> d * 1
'B * 1'
>>> d * "x"
'A * x'
然而,我实际上希望 C
的行为与 D
相同,但事实并非如此:
>>> c = C()
>>> c * 1
'B * 1'
>>> c * "x"
Traceback (most recent call last):
File "<ipython-input-23-549ffa5b5ffb>", line 1, in <module>
c * "x"
TypeError: can't multiply sequence by non-int of type 'C'
我明白发生了什么,当然:我只是 returning mro 中第一个匹配方法的结果(我只是希望 NotImplemented
会被处理为特殊值)
我的问题是,是否有任何方法可以避免编写像 D.__mul__
这样的样板代码(对于所有 classes,所有数值特殊方法基本上都是相同的)。我想我可以写一个 class 装饰器或 metaclass 来自动生成所有这些方法,但我希望会有一些更简单的(标准库)方法,或者,有人已经做了一些事情像这样。
Python 在您要求时走上 MRO,这并不意味着继续检查更高。将您的代码更改为使用 super()
的合作继承(将 MRO 移动到下一个 class 的请求),否则 return NotImplemented
它应该可以工作。它完全不需要 C
或 D
来定义 __mul__
,因为它们不会为其功能添加任何内容:
class A():
def __mul__(self, other):
return "A * %s" % other
class B():
def __mul__(self, other):
if isinstance(other, int):
return "B * %s" % other
try:
return super().__mul__(other) # Delegate to next class in MRO
except AttributeError:
return NotImplemented # If no other class to delegate to, NotImplemented
class C(B, A):
pass
class D(B, A):
pass # Look ma, no __mul__!
然后测试:
>>> d = D()
>>> d * 1
'B * 1'
>>> d * 'x'
'A * x'
super()
的神奇之处在于它甚至可以在多重继承场景中工作,在这种情况下,class、B
对 A
一无所知,但会如果 child 碰巧从两者继承,仍然很乐意委托给它(或任何其他可用的 class)。如果没有,我们将像以前一样处理结果 AttributeError
以生成结果 NotImplemented
,所以像这样的东西会按预期工作(它会尝试 str
的 __rmul__
不识别非int
并爆炸):
>>> class E(B): pass
>>> e = E()
>>> e * 1
'B * 1'
>>> e * 'x'
Traceback (most recent call last)
...
TypeError: can't multiply sequence by non-int of type 'E'