概要
python中的生成器是一種特殊的迭代器,如果按照c語言的說法,就是一種特殊的指針,但是python語言的一個語言特性是兼容了函數化編程,類似lambda匿名函數機制。
本文重點介紹生成器表達式的使用,是一種很快捷,節約內存的寫法。
生成器(Generator)是一種特殊的迭代器,它能讓你用一種非常高效的方式遍歷序列。它的核心思想是:按需生成數據,而不是一次性創建所有數據。
想象一下你有一百萬個數字要處理。如果你把它們全部加載到內存中,可能會導致程序變慢甚至崩潰。生成器就像一個“懶惰”的工廠,它只在你需要下一個數字時,才生產并提供給你,用完就丟棄,因此它能大大節省內存。
生成器和普通函數的區別
生成器看起來像一個普通的函數,但它最大的不同是使用 yield
關鍵字而不是 return
。
return
:當你調用一個普通函數時,它會執行到return
語句,然后返回一個值,并徹底結束。下次再調用這個函數時,它會從頭開始執行。yield
:當你調用一個生成器函數時,它會執行到yield
語句,然后返回一個值并暫停。它的狀態(包括所有局部變量和執行位置)會被保存下來。下次你再次要求它提供值時,它會從上次暫停的地方繼續執行,直到遇到下一個yield
。
怎么創建生成器?
有兩種主要方式創建生成器:
1. 生成器函數
這是最常見的方式,和定義普通函數一樣,只是將 return
換成 yield
。
def simple_generator():yield 1yield 2yield 3# 調用生成器函數,會返回一個生成器對象
gen = simple_generator()# 每次調用 next(),它都會繼續執行到下一個 yield
print(next(gen)) # 輸出: 1
print(next(gen)) # 輸出: 2
print(next(gen)) # 輸出: 3# 當沒有更多的 yield 語句時,會引發 StopIteration 異常
# print(next(gen)) # 這行會報錯
通常,我們會用 for
循環來遍歷生成器,因為 for
循環會自動處理 StopIteration
異常。
for item in simple_generator():print(item)
# 輸出:
# 1
# 2
# 3
2. 生成器表達式
偽代碼演示:
(你想生成什么 for 你要迭代什么 in 哪個序列 if 你的條件)
這是一種更簡潔的寫法,類似于列表推導式,但用圓括號 ()
而不是方括號 []
。
列表推導式(List Comprehension):一次性生成所有數據,并存入列表。
squares_list = [x*x for x in range(10)]
print(squares_list) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
生成器表達式(Generator Expression):按需生成數據,返回一個生成器對象。
squares_gen = (x*x for x in range(10))
print(squares_gen) # <generator object <genexpr> at ...># 只有當你遍歷它時,數據才會被生成
for item in squares_gen:print(item)
生成器與列表的區別
從整體來看,區別最大的就是生成器的內存占用是很小的,下面看一個例子:
列表 (List) 的內存消耗
當你使用列表推導式或手動構建一個列表時,Python 會立即分配足夠的內存來存儲所有的元素。
例如:
all_squares = [x*x for x in range(1, 1000001)]
要執行這行代碼,Python 會一次性計算 100 萬個數字的平方,并將它們全部存儲在一個列表中。如果每個整數占用 4 字節,那么這個列表至少需要 1,000,000 * 4 = 4 MB
的內存,再加上列表本身的一些開銷。
這個過程是立即的、一次性的。這很方便,但如果數據量非常大,它可能會耗盡你的系統內存,導致程序崩潰或運行緩慢。
生成器 (Generator) 的內存消耗
與列表不同,生成器是惰性求值的。它不會一次性計算并存儲所有元素。它只在需要時才計算下一個值。
例如:
all_squares_gen = (x*x for x in range(1, 1000001))
當你執行這行代碼時,Python 并沒有做任何計算。它只是創建了一個生成器對象。這個對象只存儲了如何計算下一個值的指令(即 x*x
)以及當前的狀態(即 x
的值)。
當你開始迭代這個生成器時,比如在一個 for
循環中,它會一個接一個地生成值,并且只在內存中保留當前正在使用的那個值。
類型 | 工作原理 | 內存使用 |
列表 | 一次性創建并存儲所有元素。 | 消耗大量內存,與元素數量成正比。 |
生成器 | 逐個按需生成元素。 | 消耗極少內存,與元素數量無關。 |
生成器有這樣的優點,歸功于python這樣高級語言的自動垃圾回收機制
生成器:即時分配,即時銷毀
當生成器在 for
循環中被調用時,它會:
計算下一個值。
返回這個值。
立即釋放與這個值相關的內存。
上面的例子里,生成器每計算一次,就會把上一次的計算好的數據刪除回收,節約了空間。
生成器具體應用例子
下面是兩端程序,實現的內容是一樣的,但是第一個使用一般的函數定義寫的,第二個是用匿名函數lambda和生成器寫的,比較兩者的區別
def count_fives(n):"""Return the number of values i from 1 to n (including n)where sum_digits(n * i) is 5.>>> count_fives(10) # Among 10, 20, 30, ..., 100, only 50 (10 * 5) has digit sum 51>>> count_fives(50) # 50 (50 * 1), 500 (50 * 10), 1400 (50 * 28), 2300 (50 * 46)4"""i = 1count = 0while i <= n:if sum_digits(n * i) == 5:count += 1i += 1return countdef count_primes(n):"""Return the number of prime numbers up to and including n.>>> count_primes(6) # 2, 3, 53>>> count_primes(13) # 2, 3, 5, 7, 11, 136"""i = 1count = 0while i <= n:if is_prime(i):count += 1i += 1return count
第二段
def sum_digits(y):"""Return the sum of the digits of non-negative integer y."""total = 0while y > 0:total, y = total + y % 10, y // 10return totaldef is_prime(n):"""Return whether positive integer n is prime."""if n == 1:return Falsek = 2while k < n:if n % k == 0:return Falsek += 1return Truedef count_cond(condition):"""Returns a function with one parameter N that counts all the numbers from1 to N that satisfy the two-argument predicate function Condition, wherethe first argument for Condition is N and the second argument is thenumber from 1 to N.>>> count_fives = count_cond(lambda n, i: sum_digits(n * i) == 5)>>> count_fives(10) # 50 (10 * 5)1>>> count_fives(50) # 50 (50 * 1), 500 (50 * 10), 1400 (50 * 28), 2300 (50 * 46)4>>> is_i_prime = lambda n, i: is_prime(i) # need to pass 2-argument function into count_cond>>> count_primes = count_cond(is_i_prime)>>> count_primes(2) # 21>>> count_primes(3) # 2, 32>>> count_primes(4) # 2, 32>>> count_primes(5) # 2, 3, 53>>> count_primes(20) # 2, 3, 5, 7, 11, 13, 17, 198""""*** YOUR CODE HERE ***"return lambda n: sum(1 for i in range(1,n+1) if condition(n,i))
套用生成器表達式:
(你想生成什么 for 你要迭代什么 in 哪個序列 if 你的條件)
這個代碼意味著遍歷1到n的序列,如果條件滿足condition,則生成1,而外部的sum()函數就會把1加起來,實現計數。
一行就解決了這個問題,函數式編程的泛用性可見一斑
為什么使用生成器?
節省內存:這是最大的優勢。它不會一次性把所有數據都存到內存里,特別適合處理大數據集。
延遲計算:數據只在需要時才被生成,這對于某些計算密集型任務非常有用。
可讀性高:生成器函數比編寫一個自定義迭代器類要簡單得多。
總結
生成器是按需生成數據的迭代器。
用
yield
關鍵字創建生成器函數。用圓括號
()
創建生成器表達式。它們的主要優點是內存高效和延遲計算。
如果你需要處理大量數據或者進行無限序列的操作,生成器是 Python 中一個非常有用的工具。