为 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 它应该可以工作。它完全不需要 CD 来定义 __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、BA 一无所知,但会如果 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'