【问题标题】:Walk MRO for Python special methods returning NotImplementedWalk MRO for Python 特殊方法返回 NotImplemented
【发布时间】:2017-12-14 02:17:44
【问题描述】:

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

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'

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

我的问题是,是否有办法绕过编写像 D.__mul__ 这样的样板代码(对于所有数字特殊方法,对于所有类,这基本上是相同的)。我想我可以编写一个类装饰器或元类来自动生成所有这些方法,但我希望有一些更简单的(标准库)方式,或者有人已经做过这样的事情。

【问题讨论】:

    标签: python oop multiple-inheritance method-resolution-order


    【解决方案1】:

    当您要求 Python 时,它会执行 MRO,这并不意味着继续检查更高。更改代码以使用 super() 的协作继承(将 MRO 带到下一个类的请求),否则您将返回 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() 的魔力在于,它甚至可以在多个继承场景中工作,其中一个类(在这种情况下为 B)对 A 一无所知,但仍会愉快地委托给它(或任何其他可用的类)如果一个孩子恰好继承了两者。如果不是,我们将处理结果AttributeError 以使结果NotImplemented 像以前一样,所以像这样的东西按预期工作(它尝试str__rmul__ 不识别非@ 987654335@和爆炸):

    >>> 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'
    

    【讨论】:

    • 是的,就是这样!一看就懂!
    猜你喜欢
    • 1970-01-01
    • 2017-04-08
    • 2015-02-11
    • 1970-01-01
    • 2018-09-03
    • 2020-02-12
    • 2014-05-14
    • 2019-03-16
    • 1970-01-01
    相关资源
    最近更新 更多