可迭代对象(iterable): 一般像list, tuple, dictionary这种,内部需要实现__iter__方法,该方法用于创建一个迭代器。
迭代器(iterator): 由可迭代对象创建,在for循环刚开始时自动创建,也可以通过iter(iterable)内置函数来创建。 其内部需要实现__next__方法。
可以把迭代器当成是对可迭代对象的流式处理,循环时,就像代表着可迭代对象中的每一个元素一样。
实际内部运行机制: 循环开始时,内部会调用可迭代对象的__iter__方法来创建一个迭代器,随后,迭代器通过每次执行一下其内部的__next__方法,来返回一个值,这个__next__方法可由我们自己来实现,当然我们的目的就是让其返回可迭代对象中的值。 同时我们要在方法内部实现停止循环的条件,即抛出StopIteration异常,这就标志着已经迭代完了所有元素。
先看下python对可迭代对象和迭代器的判断标准:
python内部将实现了__iter__
方法的对象叫做可迭代对象。 将同时实现了__iter__
和__next__
两个方法的对象称为迭代器。
from collections.abc import Iterable, Iterator
class Foo:
def __iter__(self):
pass
print(issubclass(Foo, Iterable))
print(issubclass(Foo, Iterator))
print('-' * 25, '分隔线', '-' * 25)
class Bar:
def __iter__(self):
pass
def __next__(self):
pass
print(issubclass(Bar, Iterable))
print(issubclass(Bar, Iterator))
输出结果:
True
False
------------------------- 分隔线 -------------------------
True
True
其实我们的迭代器如果不实现__iter__
方法,也能进行迭代。 但是它就不能像可迭代对象一样放在for ... in 的后边。
下面使用链表结构模拟可迭代对象和迭代器。 这里的迭代器我只实现了__next__
方法,没有实现__iter__
方法。
class Node:
def __init__(self, value):
self.value = value
self.next_node = None
def __iter__(self):
return NodeIterator(self)
class NodeIterator:
def __init__(self, node: Node):
self.current_node = node
def __next__(self):
node, self.current_node = self.current_node, self.current_node.next_node
return node
node1 = Node('first')
node2 = Node('second')
node3 = Node('third')
node1.next_node = node2
node2.next_node = node3
# 不使用for loop循环
it = iter(node1) # 使用iter函数作用在可迭代对象上,创建一个迭代器
print(it.current_node.value)
print('-' * 25, '分隔线', '-' * 25)
first_node = next(it)
print(first_node.value)
second_node = next(it)
print(second_node.value)
third_node = next(it)
print(third_node.value)
# forth_node = next(it) # 继续使用next操作将抛出StopIteration异常
输出结果:
first
------------------------- 分隔线 -------------------------
first
second
third
可以看到,我们使用iter函数得到了一个迭代器,然后在使用next函数作用于这个迭代器,每次都返回一个值。
下面我们使用for循环:
# 下面使用循环操作
print('-' * 25, '分隔线', '-' * 25)
for item in node1:
print(item.value)
输出结果:
------------------------- 分隔线 -------------------------
first
second
third
Traceback (most recent call last):
File "F:\RolandWork\PythonProjects\studyPython\test.py", line 42, in <module>
for item in node1:
File "F:\RolandWork\PythonProjects\studyPython\test.py", line 14, in __next__
node, self.current_node = self.current_node, self.current_node.next_node
^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'next_node'
可以看到,for循环打印出了这三个节点的值,但最终报错了。原因是迭代器已经指向最后一个节点之后,我们的next函数再执行时,它已经是None了,就没有next_node属性了。 因此这时候,我们应该手动抛出StopIteration异常,这样for循环就能正常停止了。 我们只需要像下面这样修改一个迭代器的__next__
方法。
class NodeIterator:
def __init__(self, node: Node):
self.current_node = node
def __next__(self):
if self.current_node is None:
raise StopIteration
node, self.current_node = self.current_node, self.current_node.next_node
return node
再次运行,for循环就不报错,而是正常结束循环了。
看起来我们的迭代器工作正常。 但是为什么官方会要求迭代器自身也要实现__iter__
方法呢?(即让迭代器也是一个可迭代对象)
我们来看下面这种情况:
it = iter(node1)
node = next(it) # 这行代码运行后,迭代器已经指向了第二个节点
print(node.value)
print('-' * 25, '分隔线', '-' * 25)
# 这时我们想接下来使用for循环从当前指向的节点继续迭代下去
for item in it:
print(item.value)
输出结果:
first
------------------------- 分隔线 -------------------------
Traceback (most recent call last):
File "F:\RolandWork\PythonProjects\studyPython\test.py", line 34, in <module>
for item in it:
TypeError: 'NodeIterator' object is not iterable
可以看到,运行报错了。 看样子我们不能直接把迭代器放在for循环in的后边。 这是因为,in后面要放一个可迭代对象,因为像我们一开始说的,for循环内部开始时,会先对我们in后面的对象执行iter()方法,来返回一个迭代器。而我们的迭代器目前没有实现__iter__
方法,所以它不算是可迭代对象。
如果我们想这么用,就在迭代器里实现__iter__
方法,而实现也很简单,就直接返回它自己就可以了。这样,迭代器自己返回了自己,当前指向哪个节点它当然也记得住。
在迭代器NodeIterator类中添加如下方法就可以了。
def __iter__(self): # 迭代器也实现__iter__方法,以便可以用在for in 后边
return self
修改后再次运行,结果如下:
first
------------------------- 分隔线 -------------------------
second
third
现在还有一个问题,就是当我们运行完一遍for循环后,再对这个迭代器运行一次for循环,我们发现就没有再打印任何节点了。 这与我们认为的for循环每次都会从头开始打印一遍的想法不一样。
it = iter(node1)
node = next(it) # 这行代码运行后,迭代器已经指向了第二个节点
print(node.value)
print('-' * 25, '分隔线', '-' * 25)
# 这时我们想接下来使用for循环从当前指向的节点继续迭代下去
for item in it:
print(item.value)
print('-' * 25, '分隔线', '-' * 25)
for item in it:
print(item.value)
输出结果:
first
------------------------- 分隔线 -------------------------
second
third
------------------------- 分隔线 -------------------------
而如果我们把第二次for循环的in后面的it换成node1,或者iter(node1),就可以正常打印第二次循环的内容。
it = iter(node1)
node = next(it) # 这行代码运行后,迭代器已经指向了第二个节点
print(node.value)
print('-' * 25, '分隔线', '-' * 25)
# 这时我们想接下来使用for循环从当前指向的节点继续迭代下去
for item in it:
print(item.value)
print('-' * 25, '分隔线', '-' * 25)
for item in node1: # 或者换成iter(node1)
print(item.value)
输出结果:
first
------------------------- 分隔线 -------------------------
second
third
------------------------- 分隔线 -------------------------
first
second
third
这是因为,换成node1或iter(node1)时,迭代器对象被使用iter()方法在循环一开始重新获取到了。 而之前我们的迭代器对象(it)已经指向最后一个节点了,再对它循环,就相当于再对它执行next()函数,内部就会立刻遇到StopIteration异常,从而结束循环。 为了让迭代器对象可以重复使用,我们可以在每次其指向最后一个节点,并在遇到StopIteration异常之前,重新让它指向第一个节点,为再次使用for循环做准备。
于是可以像下面这样修改代码: 在抛出异常前,加一句 self.current_node = node1
class NodeIterator:
def __init__(self, node: Node):
self.current_node = node
def __next__(self):
if self.current_node is None:
self.current_node = node1 # 每次迭代到最后一个节点时,再将current_node属性指回第一个节点,这样下次循环迭代器,就可以又重头开始。
raise StopIteration
node, self.current_node = self.current_node, self.current_node.next_node
return node
def __iter__(self): # 迭代器也实现__iter__方法,以便可以用在for in 后边
return self
标签:__,node,迭代,self,生成器,iter,next
From: https://www.cnblogs.com/rolandhe/p/18407670