Python 旋風之旅—生成器

生成器

在這裏,我們將更深入地研究 Python 生成器,包括生成器表達式和生成器函數。

 

生成器表達式

列表推導和生成器表達式之間的差異有時令人困惑; 在這裏,我們將快速概述之間的區別:

 

列表推導使用方括號,而生成器表達式使用括號

這是一個代表性的列表推導:

[n ** 2 for n in range(12)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

這是一個代表性的生成器表達式:

(n ** 2 for n in range(12))
<generator object <genexpr> at 0x104a60518>

注意,打印生成器表達式不會打印其內容。 一種打印生成器表達式內容的方法是將其傳遞給列表構造函數:

G = (n ** 2 for n in range(12))
list(G)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

列表是值的集合,而生成器是產生值的方法

創建列表時,實際上是在構建值的集合,與此相關的還伴隨着一些內存開銷。 創建生成器時,您不是在構建值的集合,而是構建生成這些值的方法。 兩者都向外透露了相同的迭代器接口,如下所示:

L = [n ** 2 for n in range(12)]
for val in L:
    print(val, end=' ')
0 1 4 9 16 25 36 49 64 81 100 121 
G = (n ** 2 for n in range(12))
for val in G:
    print(val, end=' ')
0 1 4 9 16 25 36 49 64 81 100 121 

不同之處在於,生成器表達式直到需要它們時才真正計算這些值。 這不僅會提高存儲效率,而且還會提高計算效率! 這也意味着,列表的大小受可用內存的限制,但生成器表達式的大小不受限制!

一個無限生成器的例子是通過 itertools 中定義的 count 迭代器創建一個無限生成器。

from itertools import count
count()
count(0)
for i in count():
    print(i, end=' ')
    if i >= 10: break
0 1 2 3 4 5 6 7 8 9 10 

count 迭代器將永遠快樂地計數,直到您告訴它停止爲止。 這使得創建無限生成器變得很方便:

factors = [2, 3, 5, 7]
G = (i for i in count() if all(i % n > 0 for n in factors))
for val in G:
    print(val, end=' ')
    if val > 40: break
1 11 13 17 19 23 29 31 37 41 

您可能會理解這裏得到的結果:如果我們適當地擴展因子列表,那麼我們開始得到的是使用 Sieve of Eratosthenes 算法的素數生成器。 我們將對此進行探討。

 

一個列表可以被迭代多次; 生成器表達式是一次性的

這是生成器表達式的潛在陷阱之一。使用列表,我們可以直接這樣做:

L = [n ** 2 for n in range(12)]
for val in L:
    print(val, end=' ')
print()

for val in L:
    print(val, end=' ')
0 1 4 9 16 25 36 49 64 81 100 121 
0 1 4 9 16 25 36 49 64 81 100 121

另一方面,生成器表達式在一次迭代後就用完了:

G = (n ** 2 for n in range(12))
list(G)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
list(G)
[]

這可能非常有用,因爲它意味着可以停止和繼續啓動迭代:

G = (n**2 for n in range(12))
for n in G:
    print(n, end=' ')
    if n > 30: break

print("\ndoing something in between")

for n in G:
    print(n, end=' ')
0 1 4 9 16 25 36 
doing something in between
49 64 81 100 121 

我發現這很有用的一個地方是使用磁盤上的數據文件集合時。 這意味着您可以輕鬆地批量分析它們,讓生成器跟蹤您尚未看到的那些。

 

生成器函數:使用yield

我們在上一節中看到,列表推導最適合用於創建相對簡單的列表,而在更復雜的情況下使用普通的for循環會更好。 生成器表達式也是如此:我們可以使用生成器函數(使用yield語句)來創建更復雜的生成器。

在這裏,我們有兩種構造相同列表的方法:

L1 = [n ** 2 for n in range(12)]

L2 = []
for n in range(12):
    L2.append(n ** 2)

print(L1)
print(L2)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

同樣,這裏有兩種構造等效生成器的方法:

G1 = (n ** 2 for n in range(12))

def gen():
    for n in range(12):
        yield n ** 2

G2 = gen()
print(*G1)
print(*G2)
0 1 4 9 16 25 36 49 64 81 100 121
0 1 4 9 16 25 36 49 64 81 100 121

生成器函數是一個函數,它不使用 return 返回一次值,而是使用 yield 生成(可能無限)值序列。 就像在生成器表達式中一樣,生成器的狀態在部分迭代之間保留,但是如果我們想要生成器的新副本,則可以簡單地再次調用該函數。

 

示例:素數生成器

在這裏,我將展示我最喜歡的生成器函數示例:生成無限系列素數的函數。 一種經典的算法是 Sieve of Eratosthenes,其工作原理如下:

# Generate a list of candidates
L = [n for n in range(2, 40)]
print(L)
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
# 移除能被 2 整除的數
# Remove all multiples of the first value
L = [n for n in L if n == L[0] or n % L[0] > 0]
print(L)
[2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39]
# 移除能被 3 整除的數
# Remove all multiples of the second value
L = [n for n in L if n == L[1] or n % L[1] > 0]
print(L)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35, 37]
# 移除能被 5 整除的數
# Remove all multiples of the third value
L = [n for n in L if n == L[2] or n % L[2] > 0]
print(L)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

如果我們在足夠大的列表上重複執行此過程足夠的次數,則可以生成任意數量的質數。

讓我們將此邏輯封裝在一個生成器函數中:

def gen_primes(N):
    """Generate primes up to N"""
    primes = set()
    for n in range(2, N):
        if all(n % p > 0 for p in primes):
            primes.add(n)
            yield n

print(*gen_primes(100))
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

到這裏就介紹完生成器了! 當然這肯定不是 Sieve of Eratosthenes 算法計算最有效的實現,但它說明了生成器函數語法對於構建更復雜的序列有多麼方便。

 

本文來自翻譯如下文章,僅用於學習

原文:

https://nbviewer.jupyter.org/github/jakevdp/WhirlwindTourOfPython/blob/master/12-Generators.ipynb#Example:-Prime-Number-Generator

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章