我正在阅读学习Python第5版,并且我需要对此段进行更多说明:
例如,字符串的__add__方法真正执行连接;尽管内部通常不应该自己使用第二种形式,但Python内部将以下第一种形式映射到第二种形式(它不那么直观,甚至可能运行得更慢):
>>> S+'NI!'
'spamNI!'
>>> S.__add__('NI!')
'spamNI!'
所以我的问题是,为什么运行速度会变慢?
答案 0 :(得分:7)
>>> def test(a, b):
... return a + b
...
>>> def test2(a, b):
... return a.__add__(b)
...
>>> import dis
>>> dis.dis(test)
2 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 BINARY_ADD
7 RETURN_VALUE
>>> dis.dis(test2)
2 0 LOAD_FAST 0 (a)
3 LOAD_ATTR 0 (__add__)
6 LOAD_FAST 1 (b)
9 CALL_FUNCTION 1
12 RETURN_VALUE
1条BINARY_ADD
指令,而不是2条指令:LOAD_ATTR
和CALL_FUNCTION
。而且由于BINARY_ADD
(几乎)做同样的事情(但在C语言中),所以我们可以期望它(略)更快。不过,这种差异几乎不会引起注意。
旁注:,所以这与汇编的工作方式相似。通常,只有一条指令执行与一系列指令相同的操作时,它会更好地执行。例如,在x64 LEA
中,指令可以替换为其他指令序列。但是他们的表现不佳。
但是有一个陷阱(这说明了为什么我开始谈论x64汇编的原因)。有时,一条指令实际上会表现更差。参见臭名昭著的LOOP instruction。这种违反直觉的行为可能有很多原因,例如:有点不同的假设,未优化的实现,历史原因,错误等,等等。
结论:在Python +
中,理论上应该比__add__
快,但始终可以测量。
答案 1 :(得分:4)
可能是解释说+
运算符实际上会在后台调用__add__
。因此,当您执行S + 'NI!'
时,实际上会__add__
被调用( if S
有一个)。因此,从语义上讲,两个版本的功能完全相同。
尽管区别在于代码对应的内容。您可能知道,Python被编译成字节码,然后执行。字节码操作决定了解释器必须执行的步骤。您可以使用dis
模块查看字节码:
>>> import dis
>>> dis.dis("S+'NI!'")
1 0 LOAD_NAME 0 (S)
2 LOAD_CONST 0 ('NI!')
4 BINARY_ADD
6 RETURN_VALUE
>>> dis.dis("S.__add__('NI!')")
1 0 LOAD_NAME 0 (S)
2 LOAD_METHOD 1 (__add__)
4 LOAD_CONST 0 ('NI!')
6 CALL_METHOD 1
如您所见,这里的区别基本上是+
运算符只是执行BINARY_ADD
而__add__
调用加载实际方法并执行它。
当解释器看到BINARY_ADD
时,它将自动查找__add__
实现并调用该实现,但与必须在Python字节码中查找该方法相比,它可以更有效地实现该功能。 / p>
因此,基本上,通过显式调用__add__
,可以防止解释器更快地转到实现。
话虽这么说,差异可忽略不计。如果您计时两个呼叫之间的时间差,您可以看到,但实际上并没有那么多(这是1000万次呼叫):
>>> timeit("S+'NI!'", setup='S = "spam"', number=10**7)
0.45791053899995404
>>> timeit("S.__add__('NI!')", setup='S = "spam"', number=10**7)
1.0082074819999889
请注意,这些结果不一定总是看起来像这样。计时自定义类型(使用非常简单的__add__
实现)时,对__add__
的调用可能会更快:
>>> timeit("S+'NI!'", setup='from __main__ import SType;S = SType()', number=10**7)
0.7971681049998551
>>> timeit("S.__add__('NI!')", setup='from __main__ import SType;S = SType()', number=10**7)
0.6606798959999196
这里的差异更小,但+
慢。
最重要的是,您不必担心这些差异。选择更具可读性的内容,几乎所有时间都是+
。如果您需要担心性能,请确保从整体上分析您的应用程序,并且不要相信这种微基准。当您查看您的应用程序时,它们没有帮助,并且99.99%的情况下,这两种方法之间的差异不会有所不同。您的应用程序中还有另一个瓶颈会使其运行速度进一步降低。