首先我們要先來聊聊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
。