35. 迭代如何工作:可迭代对象与迭代器
在本书中,你一直在使用 for 循环(loop)来遍历列表、字符串、字典以及其他集合。你无数次写过类似 for item in my_list: 的代码。但当 Python 执行一个 for 循环时,背后究竟发生了什么?Python 是如何知道该如何逐步遍历不同类型的集合的?
在本章中,我们将探索 Python 的迭代协议(iteration protocol)——使 for 循环得以工作的机制。你将学习可迭代对象(iterables)(你可以循环遍历的对象)和迭代器(iterators)(真正负责逐步取出值的对象)。理解这一区别将加深你对 Python 工作方式的认识,并为你在第 36 章学习生成器做好准备。
35.1) 对象是可迭代的意味着什么
35.1.1) 可迭代性的概念
可迭代对象(iterable) 是任何可以用 for 循环遍历的 Python 对象。我们说“遍历”,指的是 Python 可以按顺序一次从对象中取出一个元素。
你已经使用过很多可迭代对象:
# 列表是可迭代的
numbers = [1, 2, 3, 4, 5]
for num in numbers:
print(num) # Output: 1, 2, 3, 4, 5 (on separate lines)
# 字符串是可迭代的
text = "Python"
for char in text:
print(char) # Output: P, y, t, h, o, n (on separate lines)
# 字典是可迭代的(默认遍历键)
student = {"name": "Alice", "age": 20, "grade": "A"}
for key in student:
print(key) # Output: name, age, grade (on separate lines)所有这些对象——列表、字符串、字典、元组、集合、range 对象以及文件——都是可迭代对象,因为它们支持 Python 的迭代协议(iteration protocol)(一组规则,使 Python 能够对它们进行循环遍历)。
35.1.2) 是什么让对象可迭代
要让一个对象成为可迭代对象,它必须实现一个名为 __iter__() 的特殊方法。这个方法会返回一个迭代器(iterator) 对象。先不用担心细节——我们将在下一节探讨迭代器。
你可以使用内置的 iter() 函数尝试从对象获取迭代器,以检查对象是否可迭代:
# 测试对象是否可迭代
numbers = [1, 2, 3]
iterator = iter(numbers) # 可行 - 列表是可迭代的
print(type(iterator)) # Output: <class 'list_iterator'>
text = "Hello"
iterator = iter(text) # 可行 - 字符串是可迭代的
print(type(iterator)) # Output: <class 'str_iterator'>
# 尝试一个不可迭代的对象
value = 42
try:
iterator = iter(value) # 失败 - 整数不可迭代
except TypeError as e:
print(f"Error: {e}") # Output: Error: 'int' object is not iterable当你对一个可迭代对象调用 iter() 时,Python 会调用该对象的 __iter__() 方法并返回一个迭代器。如果对象没有这个方法,你会得到一个 TypeError。
35.1.3) 可迭代对象 vs 序列
理解并非所有可迭代对象都是序列(sequence) 很重要。序列是一种特定类型的可迭代对象,它支持索引并且具有确定的顺序。
# 序列支持索引
my_list = [10, 20, 30]
print(my_list[0]) # Output: 10
my_string = "Python"
print(my_string[2]) # Output: t
# 集合是可迭代的,但不是序列(不能索引,也不保证顺序)
my_set = {1, 2, 3}
for item in my_set:
print(item) # 可行 - 集合是可迭代的
# 但索引不行
try:
print(my_set[0]) # 失败 - 集合不支持索引
except TypeError as e:
print(f"Error: {e}") # Output: Error: 'set' object is not subscriptable关键区别:所有序列(列表、元组、字符串、range 对象)都是可迭代对象,但并非所有可迭代对象都是序列。集合和字典是可迭代对象,但不是序列,因为它们不支持索引。
35.1.4) 为什么可迭代性很重要
理解可迭代性可以帮助你:
- 知道可以遍历什么:任何可迭代对象都可以用于
for循环 - 理解错误信息:“object is not iterable” 表示你不能在
for循环中使用它 - 使用推导式(comprehensions):列表、集合和字典推导式都能处理任何可迭代对象
- 使用内置函数:许多内置函数如
sum()、max()、min()和sorted()接受任何可迭代对象
# 这些都能工作,因为它们接受可迭代对象
numbers = [1, 2, 3, 4, 5]
print(sum(numbers)) # Output: 15
text = "Python"
print(max(text)) # Output: y (highest alphabetically)
# 甚至也适用于集合
unique_values = {10, 5, 20, 15}
print(sorted(unique_values)) # Output: [5, 10, 15, 20]35.2) Python 中的常见迭代器(文件、range、字典等)
35.2.1) 什么是迭代器
迭代器(iterator) 是一个表示数据流的对象。每次你请求下一个元素时,它会返回一个值。一旦迭代器返回了所有值,它就会耗尽,且无法复用。
把迭代器想象成书中的书签:
- 它记得你在序列中的位置
- 你可以请求下一个元素
- 一旦到达末尾,不创建新的迭代器就无法回到开头
可迭代对象与迭代器的关键差别:
- 可迭代对象(iterable) 是你可以进行迭代的东西(比如列表)
- 迭代器(iterator) 是执行迭代的对象(逐步遍历列表的机制)
# 列表是可迭代对象
numbers = [1, 2, 3]
# 从可迭代对象获取迭代器
iterator = iter(numbers)
# 迭代器是一个独立对象
print(type(numbers)) # Output: <class 'list'>
print(type(iterator)) # Output: <class 'list_iterator'>35.2.2) for 循环中的迭代器
当你写一个 for 循环时,Python 会在幕后自动创建一个迭代器:
numbers = [10, 20, 30]
# 你写的:
for num in numbers:
print(num)
# Python 在内部做的(概念上):
# 1. 调用 iter(numbers) 获取迭代器
# 2. 反复在迭代器上调用 next()
# 3. 当迭代器抛出 StopIteration 时停止下面是显式写出来的样子:
numbers = [10, 20, 30]
# 手动迭代(for 自动完成的事情)
iterator = iter(numbers)
try:
print(next(iterator)) # Output: 10
print(next(iterator)) # Output: 20
print(next(iterator)) # Output: 30
print(next(iterator)) # Would raise StopIteration
except StopIteration:
print("No more items") # Output: No more itemsfor 循环会自动处理 StopIteration 异常,这就是为什么你在普通代码中从来不会看到它。
35.2.3) 文件对象作为迭代器
文件对象是迭代器的极佳示例。当你遍历一个文件时,它会一次读取一行:
# 创建一个示例文件
with open("students.txt", "w") as file:
file.write("Alice\n")
file.write("Bob\n")
file.write("Charlie\n")
# 逐行读取文件
with open("students.txt", "r") as file:
for line in file:
print(line.strip()) # Output: Alice, Bob, Charlie (on separate lines)文件对象既是可迭代对象也是迭代器。当你对它们调用 iter() 时,它们会返回自身:
with open("students.txt", "r") as file:
iterator = iter(file)
print(file is iterator) # Output: True (same object)
# 手动读取行
print(next(iterator)) # Output: Alice
print(next(iterator)) # Output: Bob
print(next(iterator)) # Output: Charlie这非常节省内存,因为 Python 不会把整个文件加载到内存中——它会在你请求时一次读取一行。
35.2.4) Range 对象作为迭代器
range 对象是可迭代对象,会按需生成数字:
# range 是可迭代对象
numbers = range(1, 4)
print(type(numbers)) # Output: <class 'range'>
# 从 range 获取迭代器
iterator = iter(numbers)
print(type(iterator)) # Output: <class 'range_iterator'>
# 使用迭代器
print(next(iterator)) # Output: 1
print(next(iterator)) # Output: 2
print(next(iterator)) # Output: 3range 之所以节省内存,是因为它不会把所有数字都存到内存里——它会在请求时计算每个数字:
# 这个 range 表示 100 万个数字,但使用极少内存
large_range = range(1000000)
print(type(large_range)) # Output: <class 'range'>
# 获取迭代器
iterator = iter(large_range)
print(next(iterator)) # Output: 0
print(next(iterator)) # Output: 1
# ... can continue for 1 million values35.2.5) 字典迭代器
字典为键、值以及键值对提供了不同的迭代器:
student = {"name": "Alice", "age": 20, "grade": "A"}
# 遍历键(默认)
for key in student:
print(key) # Output: name, age, grade (on separate lines)
# 显式获取键迭代器
keys_iterator = iter(student.keys())
print(next(keys_iterator)) # Output: name
print(next(keys_iterator)) # Output: age
# 遍历值
values_iterator = iter(student.values())
print(next(values_iterator)) # Output: Alice
print(next(values_iterator)) # Output: 20
# 遍历条目(键值对)
items_iterator = iter(student.items())
print(next(items_iterator)) # Output: ('name', 'Alice')
print(next(items_iterator)) # Output: ('age', 20)35.2.6) 迭代器是可耗尽的
迭代器的一个关键特性是它们只能使用一次。一旦耗尽,它们不会重置:
numbers = [1, 2, 3]
iterator = iter(numbers)
# 第一次遍历迭代器
print(next(iterator)) # Output: 1
print(next(iterator)) # Output: 2
print(next(iterator)) # Output: 3
# 迭代器现在已经耗尽
try:
print(next(iterator)) # Raises StopIteration
except StopIteration:
print("Iterator exhausted") # Output: Iterator exhausted
# 要再次迭代,需要创建一个新的迭代器
iterator = iter(numbers)
print(next(iterator)) # Output: 1 (fresh start)这不同于可迭代对象本身,可迭代对象可以被多次遍历:
numbers = [1, 2, 3]
# 第一次迭代
for num in numbers:
print(num) # Output: 1, 2, 3
# 第二次迭代(没问题 - 会创建一个新的迭代器)
for num in numbers:
print(num) # Output: 1, 2, 335.3) 使用 iter() 和 next() 逐步遍历可迭代对象
35.3.1) iter() 函数
iter() 函数接受一个可迭代对象并返回一个迭代器。这是迭代协议的第一步:
# 从不同的可迭代对象创建迭代器
numbers = [10, 20, 30]
iterator = iter(numbers)
print(type(iterator)) # Output: <class 'list_iterator'>
text = "Hi"
text_iterator = iter(text)
print(type(text_iterator)) # Output: <class 'str_iterator'>
my_set = {1, 2, 3}
set_iterator = iter(my_set)
print(type(set_iterator)) # Output: <class 'set_iterator'>每种可迭代对象都会返回它自己的专用迭代器类型,但它们的工作方式都一样——你调用 next() 来获取下一个值。
35.3.2) next() 函数
next() 函数从迭代器中取出下一个元素。当没有更多元素时,它会抛出 StopIteration:
colors = ["red", "green", "blue"]
iterator = iter(colors)
# 一次获取一个元素
print(next(iterator)) # Output: red
print(next(iterator)) # Output: green
print(next(iterator)) # Output: blue
# 没有更多元素
try:
print(next(iterator)) # Raises StopIteration
except StopIteration:
print("No more colors") # Output: No more colors35.3.3) 给 next() 提供默认值
你可以给 next() 提供一个默认值作为第二个参数。当迭代器耗尽时,next() 不会抛出 StopIteration 异常,而是返回你指定的默认值:
numbers = [1, 2, 3]
iterator = iter(numbers)
print(next(iterator)) # Output: 1
print(next(iterator)) # Output: 2
print(next(iterator)) # Output: 3
print(next(iterator, "Done")) # Output: Done (default value, no exception)
print(next(iterator, "Done")) # Output: Done (still exhausted)当你想优雅地处理迭代结束而不使用异常处理时,这会很有用:
35.4) 使用 iter 和 next 创建自定义迭代器
35.4.1) 为什么要创建自定义迭代器
Python 的内置可迭代对象(列表、字符串、文件)覆盖了大多数常见场景。不过,有时你需要为特定行为创建自己的可迭代对象:
- 使用自定义逻辑生成序列
- 遍历你设计的数据结构
- 以节省内存的方式迭代大型数据集
- 实现惰性求值(lazy evaluation)(仅在需要时才计算值)
创建自定义迭代器需要实现两个特殊方法:__iter__() 和 __next__()。
35.4.2) 迭代器协议
要让一个对象成为迭代器,它必须实现:
__iter__():返回迭代器对象本身(通常是self)__next__():返回序列中的下一个值,或在完成时抛出StopIteration
class SimpleCounter:
"""一个从 start 计数到 end 的迭代器。"""
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
"""返回迭代器对象(self)。"""
return self
def __next__(self):
"""返回下一个值,或抛出 StopIteration。"""
if self.current > self.end:
raise StopIteration
value = self.current
self.current += 1
return value
# 使用自定义迭代器
counter = SimpleCounter(1, 5)
for num in counter:
print(num)
# Output: 1
# Output: 2
# Output: 3
# Output: 4
# Output: 5让我们分解一下发生了什么:
for循环调用iter(counter),这会调用counter.__iter__()并返回counter本身- 循环反复调用
next(counter),这会调用counter.__next__() - 每次调用
__next__()都会返回下一个数字,并递增current - 当
current > end时,__next__()抛出StopIteration,循环停止
35.4.3) 手动使用自定义迭代器
你也可以用 iter() 和 next() 手动使用自定义迭代器:
counter = SimpleCounter(10, 13)
# 获取迭代器(返回自身)
iterator = iter(counter)
print(iterator is counter) # Output: True
# 手动获取值
print(next(iterator)) # Output: 10
print(next(iterator)) # Output: 11
print(next(iterator)) # Output: 12
print(next(iterator)) # Output: 13
# 现在已耗尽
try:
print(next(iterator))
except StopIteration:
print("Counter exhausted") # Output: Counter exhausted35.4.4) 迭代器是可耗尽的(再次强调)
记住,迭代器只能使用一次:
counter = SimpleCounter(1, 3)
# 第一次迭代
for num in counter:
print(num) # Output: 1, 2, 3
# 第二次迭代(不工作 - 迭代器已耗尽)
for num in counter:
print(num) # Nothing printed - iterator is already exhausted要再次迭代,你需要创建一个新实例:
# 每次迭代都创建一个新的 counter
for num in SimpleCounter(1, 3):
print(num) # Output: 1, 2, 3
for num in SimpleCounter(1, 3):
print(num) # Output: 1, 2, 3 (new iterator)35.4.5) 创建一个可迭代类(不仅仅是迭代器)
很多时候,你希望一个类是可迭代的,并且每次都创建一个全新的迭代器。为此,将可迭代对象与迭代器分离:
class CounterIterable:
"""一个可迭代对象,用于创建新的计数器迭代器。"""
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
"""每次返回一个新的迭代器。"""
return CounterIterator(self.start, self.end)
class CounterIterator:
"""实际执行计数的迭代器。"""
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current > self.end:
raise StopIteration
value = self.current
self.current += 1
return value
# 现在我们可以多次迭代
counter = CounterIterable(1, 3)
# 第一次迭代
for num in counter:
print(num) # Output: 1, 2, 3
# 第二次迭代(可行,因为 __iter__ 会创建新的迭代器)
for num in counter:
print(num) # Output: 1, 2, 3这种模式将关注点分离:
CounterIterable是可迭代对象——它知道如何创建迭代器CounterIterator是迭代器——它知道如何逐步取出值
35.4.6) 实用示例:遍历自定义数据结构
让我们为一个自定义数据结构创建迭代器——一个简单的播放列表:
class Playlist:
"""一个可被迭代的音乐播放列表。"""
def __init__(self):
self.songs = []
def add_song(self, title, artist):
"""向播放列表添加一首歌。"""
self.songs.append({"title": title, "artist": artist})
def __iter__(self):
"""返回播放列表的迭代器。"""
return PlaylistIterator(self.songs)
class PlaylistIterator:
"""用于逐首遍历播放列表歌曲的迭代器。"""
def __init__(self, songs):
self.songs = songs
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.songs):
raise StopIteration
song = self.songs[self.index]
self.index += 1
return song
# 使用播放列表
playlist = Playlist()
playlist.add_song("Imagine", "John Lennon")
playlist.add_song("Bohemian Rhapsody", "Queen")
playlist.add_song("Hotel California", "Eagles")
# 遍历歌曲
print("Now playing:")
for song in playlist:
print(f" {song['title']} by {song['artist']}")
# Output: Now playing:
# Output: Imagine by John Lennon
# Output: Bohemian Rhapsody by Queen
# Output: Hotel California by Eagles
# 可以再次遍历(会创建一个新的迭代器)
print("\nReplay:")
for song in playlist:
print(f" {song['title']}")
# Output: Replay:
# Output: Imagine
# Output: Bohemian Rhapsody
# Output: Hotel California35.4.7) 何时使用自定义迭代器
在以下情况下创建自定义迭代器:
- 你需要惰性求值(lazy evaluation):按需生成值,而不是一次性存储全部
- 你有自定义数据结构:让它可迭代,从而能够与
for循环一起工作 - 你需要特殊的迭代逻辑:跳过元素、转换值,或实现复杂的步进规则
- 内存效率很重要:生成大型序列而不存储它们
不过,在第 36 章中,你将学习生成器(generators),它通过 yield 关键字提供了一种更简单的方式来创建迭代器。与手动实现 __iter__() 和 __next__() 相比,生成器通常更受欢迎,因为它更简洁也更易理解。
理解如何创建自定义迭代器能让你洞察 Python 的迭代协议是如何工作的,即使你通常会使用生成器来替代它。在这里学到的概念——__iter__()、__next__() 和 StopIteration——是理解生成器和下一章其他高级迭代技术的基础。