为什么python不利用__iadd__来实现求和和链式运算符?

Jac*_*ack 9 python optimization python-internals

我刚做了一个有趣的测试:

~$ python3 # I also conducted this on python 2.7.6, with the same result
Python 3.4.0 (default, Apr 11 2014, 13:05:11) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo(object):
...     def __add__(self, other):
...         global add_calls
...         add_calls += 1
...         return Foo()
...     def __iadd__(self, other):
...         return self
...
>>> add_calls = 0
>>> a = list(map(lambda x:Foo(), range(6)))
>>> a[0] + a[1] + a[2]
<__main__.Foo object at 0x7fb588e6c400>
>>> add_calls
2
>>> add_calls = 0
>>> sum(a, Foo())
<__main__.Foo object at 0x7fb588e6c4a8>
>>> add_calls
6
Run Code Online (Sandbox Code Playgroud)

显然,该__iadd__方法比方法更有效__add__,不需要分配新类.如果我添加的对象足够复杂,这会产生不必要的新对象,可能会在我的代码中造成巨大的瓶颈.

我希望a[0] + a[1] + a[2]在第一个操作中调用__add__,第二个操作将调用__iadd__新创建的对象.

为什么python不优化这个?

Mar*_*ers 5

__add__方法可以自由返回不同类型的对象,而__iadd__如果使用就地语义,则应返回return self。这里不需要它们返回相同类型的对象,因此sum()不应依赖的特殊语义__iadd__

您可以使用该functools.reduce()函数自己实现所需的功能:

from functools import reduce

sum_with_inplace_semantics = reduce(Foo.__iadd__, a, Foo())
Run Code Online (Sandbox Code Playgroud)

演示:

>>> from functools import reduce
>>> class Foo(object):
...     def __add__(self, other):
...         global add_calls
...         add_calls += 1
...         return Foo()
...     def __iadd__(self, other):
...         global iadd_calls
...         iadd_calls += 1
...         return self
... 
>>> a = [Foo() for _ in range(6)]
>>> result = Foo()
>>> add_calls = iadd_calls = 0
>>> reduce(Foo.__iadd__, a, result) is result
True
>>> add_calls, iadd_calls
(0, 6)
Run Code Online (Sandbox Code Playgroud)

  • @MartijnPieters 它也可能不知道 `__add__` 的结果是一个新对象,那么这有什么关系呢?您需要将这两个操作的语义保持在其数学对应项的期望之内,但是*如果*您这样做,我不明白为什么Python不能做到这一点。不过,史蒂文对产生的运行时检查成本提出了很好的观点。 (2认同)