迭代器(Iterable)
- 簡單來說,迭代器對象(my_list)可以讓以下代碼正常工作:
for i in my_list:
...
for i in iter(my_list):
...
for i in (v for v in my_list if v is not None):
...
- 如果對象實現了
__iter__()
就可以使用迭代器。我們可以手動實現迭代協議(iterator protocol)
來實現對對象的迭代操作。值得注意的是,這里的對象特指Iterator
:
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
if __name__ == '__main__':
c = Counter(1, 5)
for i in c:
print(i)
在看這段代碼之前,讓我們先來明確一個概念:
就Python中的
迭代器
而言有兩個含義:第一個是Iterable
,第二個是Iterator
。
協議規定Iterable的__iter__()
方法會返回一個Iterator,Iterator的__next__()
方法(Python 2里是next())會返回下一個Iterator對象,如果迭代結束則拋出StopIteration異常。
同時,Iterator自己也是一種Iterable,所以也需要實現Iterable的接口,也就是__iter__()
,而Iterator的__iter__()
只需要返回自己就行了。
明確之后,我們來看看上述代碼發生了什么。首先for
語句判斷對象c
是一個Iterable
(因為實現了__iter__()
)于是調用它的__iter__()
方法返回了一個Iterator
對象,接著for
語句繼續會調用它的__next__()
方法(因為此時起作用的是Iterator
對象)返回下一個Iterator
,以此往復直到拋出StopIteration
異常。
- 但是python的
for
方法對未實現迭代協議
的對象也進行了兼容,比如我們熟悉的:for key in {"a": 1, "b": 2}: ...
這是因為對于實現了__getitem__
的對象for
方法會改用以下標迭代的方式:
class Counter:
def __init__(self, low, high):
self.low = low
self.high = high
def __len__(self):
return self.high - self.low
def __getitem__(self, key):
index = self.low + key
if self.low <= index <= self.low + len(self):
return index
else:
raise IndexError
if __name__ == '__main__':
c = Counter(1, 5)
for i in c:
print(i)
這一點與Iterator
本身無關,但是卻屬于Iterable
的范疇。
我們熟悉的Python的內建對象dict
和list
都是可迭代的(Iterable),但是它們滿足的是序列協議(sequence protocol)
故可迭代。
實際上:
iterable: 實現了
__iter__()
或__getitem__()
方法的對象。
iterator: 實現了 iterator protocol(即方法:__next__()
和__iter__()
)的iterable對象。
sequence:實現了 sequence protocol(即方法:__getitem__()
和__len__()
),并能使用整數索引訪問元素的iterable對象。
這一點可以參考 PEP 234
同時,附一張圖說明它們之間關系:
生成器(Generators)
-
首先
Generators
也是一個Iterator
對象,內部也實現了__iter__()
和__next__()
方法,我們可以使用以下方法使用生成器:- 在函數中使用
yield
:
def count_generator(): i = 0 while True: i += 1 yield i if __name__ == '__main__': a = count_generator() print(a) # <generator object count_generator at 0x7f2474adda40> print(next(a)) # 1 print(next(a)) # 2 print(next(a)) # 3
- 生成器表達式
a = (i for i in range(1, 10)) print(a) # <generator object <genexpr> at 0x01291720> print(next(a)) # 1 print(next(a)) # 2 print(next(a)) # 3
- 在函數中使用
與
Iterator
有所區別的是,Generators
迭代的本質就是通過next()
或__next__()
方法來調用send()
方法(可以參考PEP 342)。我們可以用以下方式實現類似Iterable
的迭代:
c = (i for i in range(1, 10))
for i in range(1, 10):
print(c.send(None))
- 所以既然我們可以使用
send()
方法來控制生成器的輸出,當然我們也可以實現從外部對生成器輸出的控制,來看一看下面這段沒什么用的代碼:
def generater_list(i=1):
while i < 10:
value = (yield i)
if value:
i += value
else:
i += 1
if __name__ == '__main__':
g = generater_list()
print(next(g)) # 1
print(next(g)) # 2
g.send(4)
print(next(g)) # 7
print(next(g)) # 8
在上述代碼中,send()
方法,通過yield
給value賦值并將函數掛起。當使用next()
時,生成器函數的返回值就相應的發生了改變。
如果需要直接調用 send(),第一次請務必 send(None) 只有這樣,一個 Generator 才算是真正被激活了。我們才能進行下一步操作。
-
為什么要使用生成器:
- 對內存友好
也許當處理處理上GB的文件時,將它全部讀入內存不會再是一個明智的選擇,我們可以把它放到
生成器函數
中:def file_reader(file_path): with open(file_path, "rt") as f: for line in f: yield line
- 實現協程
生成器的特性十分適合完成一些調度作業,比如說下面這個例子:
from collections import deque def count_down(n): while n > 0: print('T-minus', n) yield n -= 1 print('Blastoff!') def count_up(n): x = 0 while x < n: print('Counting up', x) yield x += 1 class TaskScheduler: def __init__(self): self._task_queue = deque() def new_task(self, task): self._task_queue.append(task) def run(self): while self._task_queue: task = self._task_queue.popleft() try: # Run until the next yield statement next(task) self._task_queue.append(task) except StopIteration: # Generator is no longer executing pass if __name__ == '__main__': sched = TaskScheduler() sched.new_task(count_down(10)) sched.new_task(count_down(5)) sched.new_task(count_up(15)) sched.run()
我們運行一下這段代碼,可以發現TaskScheduler
類在一個循環中運行生成器集合——每個都運行到碰到yield語句為止,然后馬上運行我們定義好的兩個函數中的另一個。