迭代器(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语句为止,然后马上运行我们定义好的两个函数中的另一个。
网友评论