首先我們要先來聊聊iterable與iterator。
從iterable在Python docs的說明:
... When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object. ...
我們可以定義說,如果將obj傳遞給iter,能夠順利取得iterator而不raise TypeError的話,那麼該obj就是iterable。
那iterator又該怎麼定義呢?從iterator在Python docs的說明:
... Repeated calls to the iterator’s
__next__()method (or passing it to the built-in function next()) return successive items in the stream. When no more data are available a StopIteration exception is raised instead. At this point, the iterator object is exhausted and any further calls to its__next__()method just raise StopIteration again. Iterators are required to have an__iter__()method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted. ...
我們可以總結:
next或obj的__next__連續取值。iterator是有限的,那麼iterator耗盡後再呼叫next或obj的__next__,會raise StopIteration。iterator是一種iterable。當對iterator使用iter時,將取回其iterator本身。如果滿足以上幾點的話,那麼該obj就是iterator。
iteration protocol會先看看obj是否有實作__iter__,如果有的話就嘗試是否能透過呼叫__iter__得到iterator,並利用next或iterator的__next__取值。如果沒有實作__iter__,卻滿足昨天的Sequence protocol時,會退一步使用__getitem__來取值。
如何生成iterator,我們提供以下五種方法。
iter來得到其它iterable的iterator我們可以直接使用iter(obj)來取得obj的iterator。
generator expression的寫法與list comprehensions幾乎一樣,只是將[]改成(),其回傳型態是generator,可以視為一種iterator。
於function中使用yield關鍵字,使得function成為一個generator function,其回傳型態也是generator。
於function中使用yield from關鍵字,使得function成為一個generator function,其回傳型態也是generator。
iterator class即是參照iterator基本定義,建立一個class,並照其protocol實作__iter__及__next__。
假設您在一間新創公司辛苦打拼數年,最終如願IPO,於是決定將部份股份賣出,買下幾部心儀已久的車款。身為Python高手的您,決定寫個Garage class來管理這些車子,且Garage所生成的instance必須是個iterable,可以逐個顯示當前車庫內的車子。
Garage class,裡面有一些基礎的功能來幫助管理。# 01
from contextlib import suppress
class Garage:
def __init__(self, cars=()):
self._cars = list(cars)
def __len__(self):
return len(self._cars)
def __getitem__(self, index):
return self._cars[index]
def add_car(self, car):
self._cars.append(car)
def remove_car(self, car):
with suppress(ValueError):
self._cars.remove(car)
Garage符合sequence protocol,所以其生成的instance會是一個iterable。但身為Python高手的您知道,這樣是比較沒有效率的,於是您決定試著實作上述五種生成iterator的方式。# 01
class Garage:
...
def __iter__(self):
"""method 1"""
return iter(self._cars)
self._cars是list型態,所以我們可以直接透過iter取得list的iterator回傳,其型態為list_iterator。
# 01
class Garage:
...
def __iter__(self):
"""method 2"""
return (car for car in self._cars)
self._cars是iterable,所以我們可以對其打個迴圈,使用generator expression產生一個generator。
# 01
class Garage:
...
def __iter__(self):
for car in self._cars:
yield car
self._cars是iterable,所以我們可以對其打個迴圈,利用yield關鍵字產生一個generator。
# 01
class Garage:
...
def __iter__(self):
"""method 4"""
yield from self._cars
self._cars是iterable,所以我們可以利用yield from關鍵字產生一個generator。
# 01
class Garage:
...
def __iter__(self):
"""method 5"""
return GarageIterator(self)
class GarageIterator:
def __init__(self, garage_obj):
self._garage_obj = garage_obj
self._index = 0
def __iter__(self):
return self
def __next__(self):
if self._index >= len(self._garage_obj):
raise StopIteration
car = self._garage_obj[self._index]
self._index += 1
return car
方法5我們於__iter__中回傳GarageIterator的instance,其接受一個參數self(即Garage所生成的instance)。
我們逐一比對GarageIterator所生成的instance,是否會符合我們對iterator的定義:
obj的__next__連續取值。iterator為有限的,當耗盡後若再呼叫next或obj的__next__,會raise StopIteration。__iter__會回傳self,即GarageIterator生成的instance。發現全都符合,所以GarageIterator class是一個iterator class。
方法1~5都能正常使用。
# 01
...
if __name__ == '__main__':
garage = Garage(['Koenigsegg Regera', 'Ford Mustang', 'Tesla Model X'])
for car in garage:
print(car)
Koenigsegg Regera
Ford Mustang
Tesla Model X
且當我們使用add_car加入新車到車庫後,__iter__也很聰明的能夠反應現況。
# 01
...
if __name__ == '__main__':
...
garage.add_car('Peugeot 308')
for car in garage:
print(car) # Peugeot 308 now in garage
Koenigsegg Regera
Ford Mustang
Tesla Model X
Peugeot 308
方法1~4是我們一般常使用的方法。方法5雖然明顯麻煩不少,但是我們可以偷偷改變iterator的狀態。
# 01
...
if __name__ == '__main__':
...
garage_iter = iter(garage)
print(next(garage_iter)) # Koenigsegg Regera
print(next(garage_iter)) # Ford Mustang
print(next(garage_iter)) # Tesla Model X
print(next(garage_iter)) # Peugeot 308
garage_iter._index = 0
print(next(garage_iter)) # Koenigsegg Regera
上面的程式中,我們先使用iter(garage)將GarageIterator生成的iterator拿在手上。接下來呼叫四次next,分別取得Koenigsegg Regera、Ford Mustang、Tesla Model X及Peugeot 308。接著我們將garage_iter._index設為0,然後再次呼叫next,就又可以再次取得Koenigsegg Regera了。
除了可以改變iterator的狀態外,我們還可以在GarageIterator內加上其它attribute或function,這是方法1~4無法做到的。
當需要生成iterator時,我們應該優先使用方法1~4,因為這幾個方法都能快速生成iterator。但當我們需要在iteration過程中改變iterator狀態,或需要有特殊的attribute或function可以使用的話,就得依照iterator的基本定義,來實作像方法5的iterator class。