我正在玩sum
函数,并观察到以下行为:
案例1:
source = """
class A:
def __init__(self, a):
self.a = a
def __add__(self, other):
return self.a + other;
sum([*range(10000)], start=A(10))
"""
import timeit
print(timeit.timeit(stmt=source))
正如你所看到的,我使用自定义类的实例作为sum
函数的start
参数。在我的系统中对上面的代码进行基准测试大约需要192.60747704200003
秒。
案例2:
source = """
class A:
def __init__(self, a):
self.a = a
def __add__(self, other):
return self.a + other;
sum([*range(10000)], start=10). <- Here
"""
import timeit
print(timeit.timeit(stmt=source))
但是如果我删除自定义类实例并直接使用int
对象,它只需要111.48285191600007
秒。我很想知道这种速度差异的原因。
我的系统信息:
>>> import platform
>>> platform.platform()
'macOS-12.5-arm64-arm-64bit'
>>> import sys
>>> sys.version
'3.11.0 (v3.11.0:deaf509e8f, Oct 24 2022, 14:43:23) [Clang 13.0.0 (clang-1300.0.29.30)]'
builtin_sum_impl内部有两个实现,如果start
是一个数字,则跳过创建python"数字对象";和c中的数字。
是另一种较慢的实现,当start
不是数字时,这会强制__add__
方法为"数字对象"。要被调用,(因为它假定您正在求和一些奇怪的类)。
你强迫它使用较慢的那个。
也许查看字节码可以帮助理解发生了什么。如果你运行
import dis
def test_range():
class A:
def __init__(self, a):
self.a = a
def __add__(self, other):
return self.a + other
sum([*range(10000)], start=10)
dis.dis(test_range)
使用start=A(10)
的版本会生成2条指令:
2 LOAD_CONST 1 (<code object A at 0x7ff0bfa25c90, file "/.../main.py", line 5>)
...
26 LOAD_CONST 4 (10)
28 LOAD_CONST 5 (('start',))
30 CALL_FUNCTION_KW 2
32 POP_TOP
34 LOAD_CONST 0 (None)
36 RETURN_VALUE
和
2 LOAD_CONST 1 (<code object A at 0x7ff0bfa25c90, file "/.../main.py", line 5>)
...
26 LOAD_FAST 0 (A) <--- here
28 LOAD_CONST 4 (10)
30 CALL_FUNCTION 1 <--- and here
32 LOAD_CONST 5 (('start',))
34 CALL_FUNCTION_KW 2
36 POP_TOP
38 LOAD_CONST 0 (None)
40 RETURN_VALUE
start=A(10)
版本的完整字节码在这里。
我(有限)的理解是,这两行指向A
的初始化。请确认一下。