第一次接触 Python 的时候,你可能写过类似 for i in [2, 3, 5, 7, 11, 13]: print(i)
这样的语句。
但是,你想过 Python 在处理 for in 语句的时候,具体发生了什么吗?什么样的对象可以被for in
来枚举呢?
for loop和迭代器
在Python中,for循环是一种常用的迭代结构,用于遍历可迭代对象
中的元素。迭代器是一种特殊的对象,它实现了迭代协议,允许按照一定的顺序逐个访问元素。
for循环和迭代器之间存在密切的关系,实际上,for循环是基于迭代器工作的。当使用for循环遍历可迭代对象时,Python会在内部自动创建一个迭代器对象,并使用该迭代器来逐个获取元素,直到所有元素都被访问完毕。
for loop
里面in后面那个东西必须是一个iterable
, 也就是必须是一个可迭代对象
。
下面是一个示例,演示了for循环和迭代器之间的关系:
fruits = ['apple', 'banana', 'orange']
# 使用for循环遍历列表元素
for fruit in fruits:
print(fruit)
# 上述代码等价于下面的迭代器方式
iterator = iter(fruits) # 创建迭代器对象
while True:
try:
fruit = next(iterator) # 获取下一个元素
print(fruit)
except StopIteration:
break
在上面的示例中,我们首先使用for循环遍历了列表fruits
中的元素,打印出每个水果的名称。然后,我们手动创建了一个迭代器对象iterator
,并使用next()
函数逐个获取元素,直到遇到StopIteration
异常,表示所有元素都被访问完毕。
可以看到,使用for循环可以简化迭代过程,不需要显式地创建迭代器对象和处理StopIteration
异常。Python的许多内置对象(如列表、元组、字典等)都是可迭代的,因此可以直接在for循环中使用它们。
列表
lst = [1, 2, 3]
for i in lst:
print(i)
字典
d = {"a": 1, "b": 2}
for i in d:
print(i)
文件操作
with open(my.txt, "r") as f:
for i in f:
print(i)
除了内置的可迭代对象,你还可以自定义迭代器类,实现自己的迭代逻辑。这样,你就可以在for循环中使用自定义的迭代器来遍历特定的数据结构或实现特定的迭代行为。
for loop
的背后核心是迭代器
和可迭代对象
。
迭代器和可迭代对象
https://docs.python.org/3/glossary.html
cpython本身并不总是遵循每个iterator都是iterable的要求。
在Python中,可迭代对象和迭代器是两个相关但不同的概念。
可迭代对象
可迭代对象(Iterable)
是指实现了__iter__()
方法的对象,或者实现了__getitem__()
方法且可按照顺序访问的对象。这两者都是为了保证它可以在iter这个函数的作用下返回一个iterator(迭代器)
。可迭代对象可以被迭代,也就是可以在for循环中使用。常见的可迭代对象包括列表、元组、字符串、字典、集合等。
判断是否是可迭代对象:
from collections.abc import Iterable
print(isinstance(123, Iterable))
print(isinstance(True, Iterable))
print(isinstance('abc', Iterable))
print(isinstance([], Iterable))
print(isinstance({}, Iterable))
print(isinstance((), Iterable))
输出结果为:
False
False
True
True
True
True
迭代器
迭代器(Iterator)
是一种特殊的对象,它实现了迭代协议,具有__iter__()
和__next__()
方法。迭代器用于逐个返回可迭代对象中的元素,每次调用__next__()
方法都会返回下一个元素,如果没有更多元素,则引发StopIteration
异常。迭代器对象还可以在迭代过程中记录迭代状态。
每当使用for循环来遍历一个可迭代对象时,Python会在内部自动创建一个迭代器对象,并调用其__next__()
方法来逐个获取元素。因此,可以说for循环是基于迭代器工作的。
下面是一个示例,演示了可迭代对象和迭代器的概念:
fruits = ['apple', 'banana', 'orange']
# fruits是可迭代对象,可以在for循环中使用
for fruit in fruits:
print(fruit)
# 创建迭代器对象
iterator = iter(fruits)
# 调用迭代器的__next__()方法获取下一个元素
print(next(iterator)) # 输出:'apple'
print(next(iterator)) # 输出:'banana'
print(next(iterator)) # 输出:'orange'
print(next(iterator)) # 引发StopIteration异常
在上面的示例中,fruits
是一个可迭代对象,我们可以直接在for循环中使用它来遍历元素。同时,我们也可以使用iter()
函数手动将可迭代对象转换为迭代器对象,并使用next()
函数来逐个获取元素。当所有元素都被访问完毕时,继续调用next()
函数会引发StopIteration
异常。
需要注意的是,迭代器是一种一次性的对象,即在迭代过程中,一旦迭代器返回了所有元素,它就会耗尽,无法再次使用。如果想重新遍历可迭代对象,需要重新创建迭代器对象。
- 一个iterable(可迭代对象)更像是一个数据的保存者,一个container,它是可以没有状态的,它可以完全不知道你这个iterator(迭代器)数到哪了,它需要有能力产生一个iterator(迭代器)。
- iterator一定是有状态的,但是它并不需要实现一个container,它当然内部肯定知道它代表这个iterable(可迭代对象)里面是什么数据。iterator(迭代器)必须要有
__next__
这个method。这个method保证它在被next作用的时候可以返回下一个iterable(可迭代对象)。
自定义一个可迭代对象和对应的迭代器-遍历链表
#! -*-conding=: UTF-8 -*-
# 2023/5/22 18:43
class NodeIter:
def __init__(self, node):
self.curr_node = node
def __next__(self):
if self.curr_node is None:
raise StopIteration
node, self.curr_node = self.curr_node, self.curr_node.next
return node
class Node:
def __init__(self, name):
self.name = name
self.next = None
def __iter__(self):
return NodeIter(self)
node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")
node1.next = node2
node2.next = node3
if __name__ == '__main__':
for node in node1:
print(node.name)
如果我们想在for loop
里面使用链表的话,那我们就要自己把链表变成一个iterable(可迭代对象)。
这段代码演示了如何自定义一个可迭代对象和对应的迭代器。
首先,我们定义了一个Node
类,表示一个节点,每个节点具有一个名称和一个指向下一个节点的引用。Node
类实现了__iter__()
方法,该方法返回一个迭代器对象。
然后,我们定义了一个NodeIter
类作为迭代器,它接收一个节点对象作为参数,并在__init__()
方法中初始化当前节点。NodeIter
类实现了__next__()
方法,用于返回下一个节点。在每次调用__next__()
方法时,它会将当前节点作为结果返回,并将当前节点更新为下一个节点。当没有更多节点时,抛出StopIteration
异常。
最后,在if __name__ == '__main__':
条件下,我们使用自定义的可迭代对象和迭代器进行遍历。通过for node in node1:
的语法,会自动调用node1
对象的__iter__()
方法获取迭代器,并通过迭代器逐个获取节点,并打印节点的名称。
运行以上代码,输出结果为:
node1
node2
node3
这个示例展示了如何自定义可迭代对象和迭代器,并在for循环中使用它们实现自定义的迭代逻辑。
如果我们想从node2开始for loop
,可能我们就会把上面的代码改吧改吧:
#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2023/5/22 21:11
class NodeIter:
def __init__(self, node):
self.curr_node = node
def __next__(self):
if self.curr_node is None:
raise StopIteration
node, self.curr_node = self.curr_node, self.curr_node.next
return node
class Node:
def __init__(self, name):
self.name = name
self.next = None
def __iter__(self):
return NodeIter(self)
node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")
node1.next = node2
node2.next = node3
if __name__ == '__main__':
it = iter(node1)
first = next(it)
for node in it:
print(node.name)
运行一下:
Traceback (most recent call last):
File "E:/vedio/golang/oldboy-liwenzhou-5/resources-master/resources/PythonStart/ss.py", line 39, in
for node in it:
TypeError: 'NodeIter' object is not iterable
纳尼?和我们想的不一样啊!
原来,it=iter(node1)
的时候返回的是iterator,而这里的iterator(迭代器)不是iterable(可迭代的)的。
那要怎么修改能使从node2
开始for loop
呢?
其实很简单,那就是让iterator(迭代器)本身也是iterable(可迭代对象):
#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2023/5/22 21:11
class NodeIter:
def __init__(self, node):
self.curr_node = node
def __next__(self):
if self.curr_node is None:
raise StopIteration
node, self.curr_node = self.curr_node, self.curr_node.next
return node
def __iter__(self):
return self
class Node:
def __init__(self, name):
self.name = name
self.next = None
def __iter__(self):
return NodeIter(self)
node1 = Node("node1")
node2 = Node("node2")
node3 = Node("node3")
node1.next = node2
node2.next = node3
if __name__ == '__main__':
it = iter(node1)
first = next(it)
print(first.name)
for node in it:
print(node.name)
输出结果为:
node1
node2
node3
可以看到,已经是我们想要的了。
让我们再看下Python官网iterator(迭代器)的描述:
至此,我们也就明白为什么python官方文档里也要求:
让每个迭代器也是可迭代的
。