学习python的同窗一般会遇到这样一道经典生成器测试题:python
def gen(): for i in range(4): yield i base = gen() for n in (2,10): base = (i+n for i in base) print(list(base))
[21,22,23,24] #简单解答: 由于for循环了两次,并对base重新赋值了,因此能够简化为(i+n for i in (i+n for i in base)) 而n 所有引用了后赋值的10。最里面的base引用的是gen。
可是这个解答并无回答一个核心问题:为何最里层的n 始终用的是10,而base能够找到以前的gen()?ide
为了简化问题,我把这道题改造了成这样:函数
a1 = 3 b = (i for i in range(a1)) a1 = 5 list(b) #[0, 1, 2] a1 = 3 b = (a1 for i in range(3)) a1= 5 list(b) #[5, 5, 5]
或许各位会猜想:这个问题可能和for后面的数据类型有关系吧?学习
但若是把range()和前面的数值都改造为列表,结果以下:测试
a1 = 1 b=([a1,] for i in range(3)) a1=2 list(b) # [[2], [2], [2]] a1 =1 b = (i for i in [a1,]) a1 = 2 list(b) # [1] #也能够把以上两个表达式结合一下 a1 = 1 b = ([a1,i] for i in [a1,]) a1 = 2 list(b) #[[2, 1]]
显而易见,当变量在for前面的时候,会引用后声明的值,而当变量在for后面的iterator中的时候会引用以前声明的值,而且与数据类型无关。spa
By the way, 可能很人多还不肯定列表自己的设定:a =1 b = [a,] a =2 print(b) #[1,]
固然以上所有是生成器表达式。若是手动定义一下生成器呢?3d
a =1 def zz(): for i in [a,]: yield [a,i] cc = zz() a=2 print(list(cc)) #[[2, 2]] #若是传入a a =1 def zz(a): for i in [a,]: yield [a,i] cc = zz(a) a=2 print(list(cc)) #[[1,1]]
生成器函数的测试结果是先后一致,不存在这个问题。code
进一步测试:blog
a = 1 c = ([b,i] for i in [a,]) b = 1 list(c) #[[1, 1]] # 可是若是a在生成器表达式后面定义的话: c = ([b,i] for i in [a,]) b = 1 a = 1 list(c) # 会报错
#p.s. 在生成器函数也不会报错
对于简单的生成器,生成器表达式更方便、更直观。那么二者的执行效率是否存在差别呢?Timeit!it
import timeit def b(): a = 9999 def c(): for i in range(a): yield i list(c()) print(timeit.timeit(stmt=b,number=1000))
import timeit def b(): a = 9999 c = (i for i in range(a)) list(c) print(timeit.timeit(stmt=b,number=1000))
结果:
函数模式 表达式模式
1.260876 1.235369
1.253225 1.238639
1.256804 1.235393
1.258575 1.238165
咱们看到生成器表达式提供的便利的确是以效率的损耗做为代价的。
进一步的验证代表:生成器表达式初始化的过程相比生成器函数须要花费更多的时间(接近2倍),可是因为初始化的时间太短,并非造成差距的主要缘由。函数模式的生成器会随着next()次数的增长在时间上逐步拉开与生成器表达式差距。调用效率的差距才是主要缘由。
生成器表达式,会在程序执行的过程当中运行for 后面的代码,并对for后面的代码进行赋值,而for以前的代码以及生成器函数并不会执行,只会进行编译。
尽管,生成器表达式代码更简洁,但在生成器初始化和生成器调用的效率上都表现出了与传统生成器函数的差距。
注:列表推导式并不存在这样的问题(固然也不该该出现)