据我所知,通过理解1创建生成器的方法有三种。
经典之一:
def f1():
g = (i for i in range(10))
变体yield
:
def f2():
g = [(yield i) for i in range(10)]
变体(在函数内部yield from
引发SyntaxError
except ):
def f3():
g = [(yield from range(10))]
这三种变体导致不同的字节码,这并不奇怪。第一个是最好的似乎是合乎逻辑的,因为它是通过理解创建生成器的专用、直接的语法。但是,它不是产生最短字节码的那个。
在 Python 3.6 中反汇编
经典生成器理解
>>> dis.dis(f1)
4 0 LOAD_CONST 1 (<code object <genexpr> at...>)
2 LOAD_CONST 2 ('f1.<locals>.<genexpr>')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 3 (10)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 STORE_FAST 0 (g)
5 18 LOAD_FAST 0 (g)
20 RETURN_VALUE
yield
变体
>>> dis.dis(f2)
8 0 LOAD_CONST 1 (<code object <listcomp> at...>)
2 LOAD_CONST 2 ('f2.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 3 (10)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 STORE_FAST 0 (g)
9 18 LOAD_FAST 0 (g)
20 RETURN_VALUE
yield from
变体
>>> dis.dis(f3)
12 0 LOAD_GLOBAL 0 (range)
2 LOAD_CONST 1 (10)
4 CALL_FUNCTION 1
6 GET_YIELD_FROM_ITER
8 LOAD_CONST 0 (None)
10 YIELD_FROM
12 BUILD_LIST 1
14 STORE_FAST 0 (g)
13 16 LOAD_FAST 0 (g)
18 RETURN_VALUE
此外,timeit
比较显示该yield from
变体是最快的(仍然使用 Python 3.6 运行):
>>> timeit(f1)
0.5334039637357152
>>> timeit(f2)
0.5358906506760719
>>> timeit(f3)
0.19329123352712596
f3
f1
大约是和的 2.7 倍f2
。
正如Leon在评论中提到的,发电机的效率最好通过它可以迭代的速度来衡量。所以我改变了三个函数,让它们遍历生成器,并调用一个虚拟函数。
def f():
pass
def fn():
g = ...
for _ in g:
f()
结果更加明显:
>>> timeit(f1)
1.6017412817975778
>>> timeit(f2)
1.778684261368946
>>> timeit(f3)
0.1960603619517669
f3
现在是 8.4 倍,是f1
9.3 倍f2
。
注意:当可迭代对象不是静态可迭代对象时,结果或多或少是相同的range(10)
,例如[0, 1, 2, 3, 4, 5]
. range
因此,速度的差异与以某种方式优化无关。
那么,这三种方式有什么区别呢?更具体地说,变体和其他两个有什么区别yield from
?
这是自然构造(elt for elt in it)
比棘手的慢的正常行为[(yield from it)]
吗?从现在开始,我应该在我的所有脚本中用后者替换前者,还是使用该yield from
构造有什么缺点?
编辑
这都是相关的,所以我不想提出一个新问题,但这变得更加陌生。我尝试比较range(10)
和[(yield from range(10))]
。
def f1():
for i in range(10):
print(i)
def f2():
for i in [(yield from range(10))]:
print(i)
>>> timeit(f1, number=100000)
26.715589237537195
>>> timeit(f2, number=100000)
0.019948781941049987
所以。现在,迭代[(yield from range(10))]
速度是迭代裸机的 186 倍range(10)
?
你如何解释为什么迭代[(yield from range(10))]
比迭代快得多range(10)
?
1:对于持怀疑态度的人,以下三个表达确实产生了一个generator
对象;试着打电话type
给他们。