在本文中,將學習如何使用Python生成器來建立疊代,了解它與疊代器和常規函式有什麼區別,以及為什麼要使用它。
在Python中構建疊代器有很多開銷; 必須使用__iter__()
和__next__()
方法實現一個類,跟蹤內部狀態,當沒有值被返回時引發StopIteration
異常。
Python生成器是建立疊代器的簡單方法。上面提到的所有開銷都由Python中的生成器自動處理。
簡單來說,生成器是返回一個可以疊代的物件(迭代器)的函式(一次一個值)。
在Python中建立生成器是相當簡單的。 它使用yield
語句而不是return
語句來定義,與正常函式一樣簡單。
如果函式包含至少一個yield
語句(它可能包含其他yield
或return
語句),那麼它將成為一個生成器函式。 yield
和return
都將從函式返回一些值。
不同的是,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 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
在上面的例子中需要注意的是,在每個呼叫之間函式會保持住變數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)
當執行程式時,將輸出結果為:
This is printed first
1
This is printed second
2
This is printed at last
3
上面的例子沒有什麼用,我們研究它只是為了瞭解在後台發生了什麼。通常,生成器功能用具有適當終止條件的迴圈實現。
我們舉一個反轉字串的生成器的例子 -
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):
在這個例子中,使用range()
函式使用for
迴圈以相反的順序獲取索引。事實證明,這個生成函式不僅可以使用字串,還可以使用其他型別的列表,元組等迭代。
使用生成器表示式,可以輕鬆建立簡單的生成器。 它使構建生成器變得容易。
與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)
我們可以看到,生成器表示式沒有立即生成所需的結果。 相反,它返回一個發生器物件,並根據需要生成專案。
# 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)
生成器表示式可以在函式內部使用。當以這種方式使用時,圓括號可以丟棄。
>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100
有幾個原因使得生成器成為有吸引力。
與其疊代器類相比,發生器可以以清晰簡潔的方式實現。 以下是使用疊代器類來實現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
上面程式碼有點長,可以使用一個生成器函式實現同樣的功能。
def PowTwoGen(max = 0):
n = 0
while n < max:
yield 2 ** n
n += 1
因為,生成器自動跟蹤的細節,它更簡潔,更乾淨。
返回序列的正常函式將在返回結果之前會在記憶體中的建立整個序列。如果序列中的專案數量非常大,這可是要消耗記憶體的。
序列的生成器實現是記憶體友好的,並且是推薦使用的,因為它一次僅產生一個專案。
生成器是表示無限資料流的絕佳媒介。 無限流不能儲存在記憶體中,由於生成器一次只能生成一個專案,因此可以表示無限資料流。
以下範例可以生成所有偶數(至少在理論上)。
def all_even():
n = 0
while True:
yield n
n += 2
生成器可用於管理一系列操作,下面使用一個例子說明。
假設我們有一個快餐連鎖店的紀錄檔檔案。 紀錄檔檔案有一列(第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))
這種管道的方式是更高效和易於閱讀的。