generator是一種函式或運算式,會回傳一種特殊的疊代器,稱為generator iterator。
Generator-iterators 是惰性的:它們不會把values存放在記憶體中,而是在需要時才_產生_值。
產生器函式和其他函式看起來沒什麼兩樣,但它包含一個或多個yield 運算式。
每個yield都會暫停程式碼執行,並保存當下的執行狀態(包含所有區域變數和 try 敘述)。
當產生器恢復執行時,會從暫停的地方接續狀態,這點和每次呼叫都會重置的一般函式不同。
產生器的建構方式和其他迴圈或遞迴函式很像,但它需要一個yield運算式,我們稍後會深入探討。
舉例來說,有個函式會從給定的數字陣列中回傳這些數字的_平方_。 以目前的寫法,必須先處理完所有輸入,才能回傳任何值:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
你可以像這樣把那個函式改寫成產生器:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
這麼做的理由是,當你不需要_一次_產生所有值時,就可以使用產生器。 這樣能節省記憶體和運算資源,因為只會計算你_目前正在處理_的那個值。
在 Python 中,產生器可以替代大多數的iterables。
這包括需要iterable/iterator作為引數的_函式_或_物件_。
如要使用squares_generator()產生器:
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
generator中的值也可以透過next()函式來產生或存取。
next()會呼叫產生器疊代器物件的__next__()方法,「推進」或求值程式碼直到它的yield運算式,接著「讓出」或回傳一個值:
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
當generator-iterator完全被取用完、沒有更多值可以回傳時,它會拋出StopIteration錯誤。
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
產生器疊代器是疊代器的一個特殊子集。
Iterators是讓我們能對_可疊代物件_進行迴圈的機制/協定。
產生器疊代器和常見 Python iterables所回傳的疊代器行為非常相似,但有幾個重要差異值得注意:
indexes。
你不能用加法或減法來參照前一個或未來的值,也不能使用方括號([])表示法或切片。len()函式一起使用,因為它們沒有長度。generator-iterator收集所有值時請務必小心!yield 運算式和return運算式非常相似。
不同於return運算式,yield會在_特定時間點_把值交給呼叫者,並暫停其餘任何值的求值或回傳,直到有人要求它們為止。
當yield被求值時,它會暫停外層函式的執行,並_在當下_回傳函式的值。
接著函式會_繼續留在作用域中_,而當__next__()被呼叫時,執行會繼續進行,直到再次遇到yield為止。
在函式之外禁止使用yield運算式。
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
產生器在許多應用中都很實用。
當你面對可能很龐大的一批值時,可能不會想把它們全部放進記憶體。 產生器可以讓你一件一件地處理較大的資料,既能節省記憶體,也能提升效能。
當某個流程或計算很_複雜_、很_耗費資源_,或是_無限_時,產生器也非常有幫助:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
現在,每當對infinite_sequence物件呼叫__next__()時,它就會回傳_前一個數字_ + 1。
Conda Airlines 是程式設計界最大的航空公司,每天有超過 10,000 個航班!
他們目前以人工方式為所有乘客分配座位,這必須自動化。
他們請_你_開發一套軟體,讓乘客座位分配自動化。 他們要求你的軟體要節省記憶體、效能要好。
Conda 想為他們的飛機產生座位字母。
一架飛機是由一排排座位組成的。
每一排有 4 個座位。
每一排的座位一律命名為A、B、C和D。
一排中的第一個座位是A,第二個座位是B,以此類推。
到達D之後,應該重新從A開始。
實作一個函式generate_seat_letters(<number>),它接受一個int,代表要產生多少個座位字母。
這個函式接著應該回傳座位字母的_可迭代物件_。
>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"
Conda 想要一套能為他們的飛機產生指定數量座位的系統。
每架飛機的每一排都有 4 個座位。
排數以數字定義,從1開始往上遞增。
座位的排序應該像這樣:1A、1B、1C、1D、2A、2B、2C、2D、3A、3B、3C、3D、……
以下是一個例子:
| x | 1 | 2 |
|---|---|---|
| 排 | 5 | 21 |
| 座位字母 | A | D |
| 結果 | 5A | 21D |
由於乘客的迷信,許多航空公司的班機上沒有 13 號_排_。 Conda Airlines 也遵循這項慣例,所以請務必_不要_產生 13 號_排_的座位。
實作一個函式generate_seats(<number>),它接受一個int,代表要產生多少個座位。
這個函式接著應該回傳這些座位的_可迭代物件_。
>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"
現在你已經有個會產生座位的函式,可以用它來把座位分配給乘客。
實作一個函式assign_seats(<passengers>),它接受一份乘客姓名的list。
這個函式接著應該回傳一個_字典_,以passenger為_鍵_,seat_number為_值_。
>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']
>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
Conda Airlines 希望每張票都有唯一的代碼。 由於他們是大型航空公司,航班非常多。 這表示會有多個航班擁有相同的座位號碼。 他們要你打造一套系統,產生一組由 12 個字元組成的唯一票券字串。
這組代碼以assigned_seat開頭,接著是flight_id。
代碼其餘部分以0s填滿。
實作一個函式generate_codes(<seat_numbers>, <flight_id>),它接受一份由seat_numbers組成的list,以及一個含有航班編號的string。
這個函式接著應該回傳一個generator,逐一產生ticket_number。
>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)
>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'