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

时间:2015-06-25 15:18:25

标签: 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

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

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

为什么没有python优化这个?

3 个答案:

答案 0 :(得分:4)

>>> from PIL import Image >>> import glob, os >>> size = 128, 128 >>> pic = glob.glob("cherngloong1.jpg") >>> im = Image.open(pic[0]) >>> im <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=2048x1365 at 0x100A63BD8> >>> im.thumbnail(size, Image.ANTIALIAS) >>> im.save("cherngloong_thumbnail", "PNG") >>> im.save("cherngloong_thumbnail1", "JPEG") 方法可以自由地返回不同类型的对象,而__add__如果使用就地语义则返回__iadd__。它们不需要在此返回相同类型的对象,因此self不应该依赖sum()的特殊语义。

您可以使用functools.reduce() function自行实现所需的功能:

__iadd__

演示:

from functools import reduce

sum_with_inplace_semantics = reduce(Foo.__iadd__, a, Foo())

答案 1 :(得分:0)

Martjin's answer提供了一个很好的解决方法,但我觉得有必要总结一下评论中散布的各种答案:

sum函数主要用于不可变类型。执行除第一个就地之外的所有添加将对具有__iadd__方法的对象创建性能改进,但检查__iadd__方法会导致更典型情况下的性能损失。 Special cases aren't special enough to break the rules

我还声明__add__可能只应在a + b + c中调用一次,其中a + b创建一个临时变量,然后在返回之前调用tmp.__iadd__(c)。但是,这会违反最不意外的原则。

答案 2 :(得分:0)

既然你正在写你的班级,你知道它__add__也可以返回同一个对象,不是吗?

因此,您可以使用+运算符和内置sum运行currying优化代码:

>>> class Foo(object):
...     def __add__(self, other):
...         global add_calls
...         add_calls += 1
...         return self

(请注意将代码传递给期望“+”成为新对象的第三方函数)