关于oop:walk mro for python特殊方法返回notimplemented

Walk MRO for Python special methods returning NotImplemented

我有一个代数对象类的层次结构,这些代数对象实现特殊的方法,如__mul____add__,并使用多重继承。我在某种程度上假设python(>=3.5)会按照方法解析顺序(mro)找到第一个不返回NotImplemented的方法。唉,情况似乎不是这样。请考虑以下最小示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

1
2
3
4
5
>>> d = D()
>>> d * 1
'B * 1'
>>> d *"x"
'A * x'

然而,我实际上希望C的行为与D的行为相同,但它没有:

1
2
3
4
5
6
7
8
>>> 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'

当然,我知道发生了什么:我只是返回MRO中第一个匹配方法的结果(我只是希望NotImplemented作为一个特殊值处理)。

我的问题是,是否有任何方法可以避免编写像D.__mul__这样的样板代码(对于所有的数值特殊方法,对于所有的类,基本上都是一样的)。我想我可以编写一个类修饰器或元类来自动生成所有这些方法,但是我希望有一种更简单(标准库)的方法,或者有人已经做了类似的事情。


当您请求时,python会向mro走去,这并不是隐式地保持更高的检查级别。更改代码以使用与super()的合作继承(请求将mro提升到下一个类),否则将返回NotImplemented并且它应该可以工作。它完全不需要CD来定义__mul__,因为它们不会为其功能添加任何内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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__!

然后测试:

1
2
3
4
5
>>> d = D()
>>> d * 1
'B * 1'
>>> d * 'x'
'A * x'

super()的魔力在于,即使在多个继承场景中,它也能工作,在这种情况下,一个类,BA一无所知,但如果一个孩子碰巧从两个类继承,它仍会很高兴地委托给它(或任何其他可用的类)。如果不这样做,我们处理结果AttributeError使结果NotImplemented和以前一样,这样的事情按预期工作(它尝试str__rmul__不识别非int并爆炸):

1
2
3
4
5
6
7
8
>>> 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'