1. 程式人生 > >Python的生成器表示式與生成器函式

Python的生成器表示式與生成器函式

有一種特殊的迭代器, 叫做生成器. 生成器有兩種, 生成器表示式與生成器函式.

生成器表示式

生成器表示式與列表推導在語法上十分相似:

  • 列表推導使用[]: [i for i in arr]
  • 生成器表示式使用(): (i for i in arr)

但是它們有著本質的不同: 列表推導在被建立時會為每個元素分配記憶體空間, 最後得到的是一個正常完整的list物件. 而生成器表示式在被建立時並不會為它的元素分配記憶體空間, 因為它的元素此時還沒有生成, 要等到被訪問時才會生成並進入記憶體空間, 即以on the fly方式生成元素.
所以, 生成器與列表推導相比, 優勢是更省記憶體, 但不支援常見len

, arr[idx]等這些常見的序列操作.

a = range(10)
list_comp = [i for i in a]
gen_exp = (i for i in a)
print '列表推導得到一個list物件:', type(list_comp)
print '生成器表示式得到一個生成器物件:', type(gen_exp)
列表推導得到一個list物件: <type 'list'>
生成器表示式得到一個生成器物件: <type 'generator'>
print '生成器不能使用len方法:'
len(gen_exp)
生成器不能使用len方法:



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-3-ef3f80c9ff21> in <module>()
      1 print '生成器不能使用len方法:'
----> 2 len(gen_exp)


TypeError: object of type 'generator' has no len()
print '生成器不能按索引訪問:'
gen_exp[0]
生成器不能按索引訪問:



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-4-b9810026b625> in <module>()
      1 print '生成器不能按索引訪問:'
----> 2 gen_exp[0]


TypeError: 'generator' object has no attribute '__getitem__'
print '生成器只支援順序訪問, 每個元素只能訪問一次:'
print '第一次訪問:'
for i in gen_exp:    
    print i
print '第二次訪問:'
for i in gen_exp:    
    print i
生成器只支援順序訪問, 每個元素只能訪問一次:
第一次訪問:
0
1
2
3
4
5
6
7
8
9
第二次訪問:

生成器函式

含有yield關鍵字的函式就是生成器函式, 它有以下幾個特點:

  1. 含有yield關鍵字, 沒有return關鍵字, 或者空return
  2. 呼叫生成器函式會返回一個生成器
  3. 呼叫生成器函式時, 它包含的程式碼並不會馬上執行, 而是等到訪問生成器元素時才一段一段地執行.

下面通過示例來逐點說明.

print '有yield時, 通過return返回值會報錯:'
def gen_func():
    yield 1
    yield 2
    yield 3
    return 3
有yield時, 通過return返回值會報錯:



  File "<ipython-input-6-b64b918a6045>", line 6
    return 3
SyntaxError: 'return' with argument inside generator
print '可以使用空的return:'
def gen_func():
    yield 1
    yield 2
    yield 3
    return 
print gen_func
print gen_func()
可以使用空的return:
<function gen_func at 0x7f713c086d70>
<generator object gen_func at 0x7f713c0825f0>
print '也可以不使用return:'
def gen_func():
    yield 1
    yield 2
    yield 3

print gen_func
print gen_func()
也可以不使用return:
<function gen_func at 0x7f713c086938>
<generator object gen_func at 0x7f713c082460>

從上面的輸出可以看出, 生成器函式本身是一個函式物件, 但它的返回值是一個生成器物件. 所以, 生成器函式是一個generator factory, yield則是一種特殊的return.

def gen_func():
    print 'yield 1...'
    yield 1
    print 'yield 2...'
    yield 2
    print 'yield 3...'
    yield 3

print '呼叫gen_func時, 不會print任何東西, 因為函式體內的程式碼不會被執行:'
gen = gen_func()
print '呼叫gen_func結束.'

print '訪問生成器內的元素時才會執行函式體內的程式碼:'
print '開始訪問生成器的元素:'
for i in gen:
    print i
print '訪問生成器的元素結束.'
呼叫gen_func時, 不會print任何東西, 因為函式體內的程式碼不會被執行:
呼叫gen_func結束.
訪問生成器內的元素時才會執行函式體內的程式碼:
開始訪問生成器的元素:
yield 1...
1
yield 2...
2
yield 3...
3
訪問生成器的元素結束.

同樣的, 通過生成器函式生成的生成器也不支援len[], 元素也只能訪問一次. 每訪問一次它的元素, 它就從上次停止執行的位置繼續執行函式程式碼直到遇到下一個yield並返回yield後面的值.

總結

  1. 生成器:

    • 一種特殊的迭代器
    • 以on the fly的方式生成元素
    • 生成的元素只能訪問一次
  2. 生成表示式: (exp(i) for i in iterable)

  3. 生成器函式:
    • 包含yield, 沒有return
    • 呼叫函式時, 直接返回一個生成器, 其函式體內的程式碼不會被執行, 訪問生成器的元素時才會被執行.
  4. 使用生成器的場景: 有大量順序訪問的元素, 但所有元素只訪問一次. 這時使用生成器可以節省大量記憶體空間.