Monkey 修补运算符重载在 Python2 和 Python3 中的行为不同

Monkey patching operator overloads behaves differently in Python2 vs Python3

考虑以下代码:

class Foo:
    def __mul__(self,other):
        return other/0
x = Foo()
x.__mul__ = lambda other:other*0.5
print(x.__mul__(5))
print(x*5)

在Python2中(使用from future import print),输出

2.5
2.5

在Python3中,输出

2.5

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-1-36322c94fe3a> in <module>()
      5 x.__mul__ = lambda other:other*0.5
      6 print(x.__mul__(5))
----> 7 print(x*5)

<ipython-input-1-36322c94fe3a> in __mul__(self, other)
      1 class Foo:
      2     def __mul__(self,other):
----> 3         return other/0
      4 x = Foo()
      5 x.__mul__ = lambda other:other*0.5

ZeroDivisionError: division by zero

我 运行 在尝试实现支持代数运算子集的类型时遇到过这种情况。例如,我需要为惰性修改乘法函数:必须推迟一些计算,直到实例与另一个变量相乘。猴子补丁在 Python 2 中有效,但我注意到它在 3 中失败了。

为什么会这样? 在 Python3?

中有什么方法可以使运算符重载更灵活?

那不是 monkeypatch。

这将是一个猴子补丁:

class Foo:
    def __mul__(self, other):
        return other / 0

Foo.__mul__ = lambda self,other: other * 0.5

x = Foo()
x*9    # prints 4.5

x.__mul__ = lambda other:other*0.5 所做的是在 x 实例上创建一个 __mul__ 属性。

然后,预计x*5会调用x.__mul__(5)。它做到了,在 Python 2.

在Python3中调用了Foo.__mul__(x, 5),所以没有使用该属性。

Python 2 会做与 Python 3 相同的事情,但事实并非如此,因为 Foo 是作为旧式 class.[=20 创建的=]

此代码等同于 Python 2 和 Python 3:

class Foo(object):
    def __mul__(self,other):
        return other/0
x = Foo()
x.__mul__ = lambda other:other*0.5
print(x.__mul__(5))
print(x*5)

这将引发异常。注意 (object).

您不能在实例级别覆盖特殊方法。基于 python 的 documentation:

For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

The rationale behind this behaviour lies with a number of special methods such as __hash__() and __repr__() that are implemented by all objects, including type objects. If the implicit lookup of these methods used the conventional lookup process, they would fail when invoked on the type object itself:

>>> 1 .__hash__() == hash(1)
True
>>> int.__hash__() == hash(int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' of 'int' object needs an argument

因此,一种简单的方法是为您的猴子修补目标定义一个常规函数,并将您的新方法分配给它:

In [45]: class Foo:
             def __init__(self, arg):
                 self.arg = arg
             def __mul__(self,other):
                 return other * self.arg
             def _mul(self, other):
                 return other/0

演示:

In [47]: x = Foo(10)

In [48]: x * 3
Out[48]: 30

In [49]: my_func = lambda x: x * 0.5

In [50]: x._mul = my
my_func  mypub/   

In [50]: x._mul = my_func

In [51]: x._mul(4)
Out[51]: 2.0