python 特別的生成器表達式

Ⅰ原由

  學習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以前的代碼以及生成器函數並不會執行,只會進行編譯。

  儘管,生成器表達式代碼更簡潔,但在生成器初始化和生成器調用的效率上都表現出了與傳統生成器函數的差距。

 

 

注:列表推導式並不存在這樣的問題(固然也不該該出現)

相關文章
相關標籤/搜索