生成器
在這裏,我們將更深入地研究 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 算法計算最有效的實現,但它說明了生成器函數語法對於構建更復雜的序列有多麼方便。
本文來自翻譯如下文章,僅用於學習
原文: