Python生成器


在本文中,將學習如何使用Python生成器來創建迭代,了解它與迭代器和常規函數有什么區別,以及為什么要使用它。

在Python中構建迭代器有很多開銷; 必須使用__iter__()__next__()方法實現一個類,跟蹤內部狀態,當沒有值被返回時引發StopIteration異常。

Python生成器是創建迭代器的簡單方法。上面提到的所有開銷都由Python中的生成器自動處理。

簡單來說,生成器是返回一個可以迭代的對象(迭代器)的函數(一次一個值)。

如何在Python中創建生成器?

在Python中創建生成器是相當簡單的。 它使用yield語句而不是return語句來定義,與正常函數一樣簡單。

如果函數包含至少一個yield語句(它可能包含其他yieldreturn語句),那么它將成為一個生成器函數。 yieldreturn都將從函數返回一些值。

不同的是,return語句完全終止函數,但yield語句會暫停函數保存其所有狀態,並在以后的連續調用中繼續執行(有點像線程掛起的意思)。

生成器函數與正常函數的差異

下面列出的是生成器函數與正常函數的區別 -

  • 生成器函數包含一個或多個yield語句。
  • 當被調用時,它返回一個對象(迭代器),但不會立即開始執行。
  • __iter__()__next__()之類的方法將自動實現。所以可以使用next()迭代項目。
  • 一旦函數退讓(yields),該函數將被暫停,並將該控制權交給調用者。
  • 局部變量及其狀態在連續調用之間被記住。
  • 最后,當函數終止時,StopIteration會在進一步的調用時自動引發。

下面的例子用來說明上述所有要點。 我們有一個名為my_gen()的生成器函數和幾個yield語句。

#!/usr/bin/python3 #coding=utf-8 def my_gen(): n = 1 print('This is printed first, n= ', n) # Generator function contains yield statements yield n n += 1 print('This is printed second, n= ', n) yield n n += 1 print('This is printed at last, n= ', n) yield n 
Python

下面給出了交互式運行結果。 在Python shell中運行它們以查看輸出 -

>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()

>>> # We can iterate through the items using next().
>>> next(a)
This is printed first, n = 1
1
>>> # Once the function yields, the function is paused and the control is transferred to the caller.

>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second, n = 2
2

>>> next(a)
This is printed at last, n = 3
3

>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration
Shell

在上面的例子中需要注意的是,在每個調用之間函數會保持住變量n的值。
與正常函數不同,當函數產生時,局部變量不會被銷毀。 此外,生成器對象只能重復一次。

要重新啟動該過程,需要使用類似於a = my_gen()的方法創建另一個生成器對象。

注意:最后要注意的是,可以直接使用帶有for循環的生成器。

這是因為,for循環需要一個迭代器,並使用next()函數進行迭代。 當StopIteration被引發時,它會自動結束。 請查看這里了解一個for循環是如何在Python中實際實現的。

# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop for item in my_gen(): print(item) 
Python

當運行程序時,將輸出結果為:

This is printed first 1 This is printed second 2 This is printed at last 3 
Python

具有循環的Python生成器

上面的例子沒有什么用,我們研究它只是為了了解在后台發生了什么。通常,生成器功能用具有適當終止條件的循環實現。

我們舉一個反轉字符串的生成器的例子 -

 length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i] # For loop to reverse the string # Output: # o # l # l # e # h for char in rev_str("hello"): print(char)def rev_str(my_str): 
Python

在這個例子中,使用range()函數使用for循環以相反的順序獲取索引。事實證明,這個生成函數不僅可以使用字符串,還可以使用其他類型的列表,元組等迭代。

Python生成器表達式

使用生成器表達式,可以輕松創建簡單的生成器。 它使構建生成器變得容易。

lambda函數一樣創建一個匿名函數,生成器表達式創建一個匿名生成函數。生成器表達式的語法與Python中的列表解析類似。 但方圓[]替換為圓括號()

列表推導和生成器表達式之間的主要區別是:列表推導產生整個列表,生成器表達式一次生成一個項目。

它們是處理方式是懶惰的,只有在被要求時才能生產項目。 因此,生成器表達式的存儲器效率高於等效列表的值。

# Initialize the list my_list = [1, 3, 6, 10] # square each term using list comprehension # Output: [1, 9, 36, 100] [x**2 for x in my_list] # same thing can be done using generator expression # Output: <generator object <genexpr> at 0x0000000002EBDAF8> (x**2 for x in my_list) 
Python

我們可以看到,生成器表達式沒有立即生成所需的結果。 相反,它返回一個發生器對象,並根據需要生成項目。

# Intialize the list my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) # Output: 1 print(next(a)) # Output: 9 print(next(a)) # Output: 36 print(next(a)) # Output: 100 print(next(a)) # Output: StopIteration next(a) 
Python

生成器表達式可以在函數內部使用。當以這種方式使用時,圓括號可以丟棄。

>>> sum(x**2 for x in my_list)
146

>>> max(x**2 for x in my_list)
100
Shell

為什么在Python中使用生成器?

有幾個原因使得生成器成為有吸引力。

1. 容易實現

與其迭代器類相比,發生器可以以清晰簡潔的方式實現。 以下是使用迭代器類來實現2的冪次序的例子。

class PowTwo: def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n > self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result 
Python

上面代碼有點長,可以使用一個生成器函數實現同樣的功能。

def PowTwoGen(max = 0): n = 0 while n < max: yield 2 ** n n += 1 
Python

因為,生成器自動跟蹤的細節,它更簡潔,更干凈。

2.內存高效

返回序列的正常函數將在返回結果之前會在內存中的創建整個序列。如果序列中的項目數量非常大,這可是要消耗內存的。

序列的生成器實現是內存友好的,並且是推薦使用的,因為它一次僅產生一個項目。

3. 表未無限流

生成器是表示無限數據流的絕佳媒介。 無限流不能存儲在內存中,由於生成器一次只能生成一個項目,因此可以表示無限數據流。

以下示例可以生成所有偶數(至少在理論上)。

def all_even(): n = 0 while True: yield n n += 2 
Python

4.管道生成器

生成器可用於管理一系列操作,下面使用一個例子說明。

假設我們有一個快餐連鎖店的日志文件。 日志文件有一列(第4列),用於跟蹤每小時銷售的比薩餅數量,我們想算出在5年內銷售的總薩餅數量。

假設一切都是字符串,不可用的數字標記為“N / A”。 這樣做的生成器實現可以如下。

with open('sells.log') as file: pizza_col = (line[3] for line in file) per_hour = (int(x) for x in pizza_col if x != 'N/A') print("Total pizzas sold = ",sum(per_hour)) 
Python

這種管道的方式是更高效和易於閱讀的。

原文出自【易百教程】,商業轉載請聯系作者獲得授權,非商業轉載請保留原文鏈接:https://www.yiibai.com/python/generator.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM