Python & AI Tutorials Logo
Python 编程

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 可以按顺序一次从对象中取出一个元素。

你已经使用过很多可迭代对象:

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() 函数尝试从对象获取迭代器,以检查对象是否可迭代:

python
# 测试对象是否可迭代
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) 很重要。序列是一种特定类型的可迭代对象,它支持索引并且具有确定的顺序。

python
# 序列支持索引
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 对象)都是可迭代对象,但并非所有可迭代对象都是序列。集合和字典是可迭代对象,但不是序列,因为它们不支持索引。

Python 对象

可迭代对象

不可迭代对象

序列

非序列可迭代对象

列表、元组、字符串、Ranges

集合、字典、文件

数字、None、布尔值

35.1.4) 为什么可迭代性很重要

理解可迭代性可以帮助你:

  1. 知道可以遍历什么:任何可迭代对象都可以用于 for 循环
  2. 理解错误信息:“object is not iterable” 表示你不能在 for 循环中使用它
  3. 使用推导式(comprehensions):列表、集合和字典推导式都能处理任何可迭代对象
  4. 使用内置函数:许多内置函数如 sum()max()min()sorted() 接受任何可迭代对象
python
# 这些都能工作,因为它们接受可迭代对象
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)执行迭代的对象(逐步遍历列表的机制)
python
# 列表是可迭代对象
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 会在幕后自动创建一个迭代器:

python
numbers = [10, 20, 30]
 
# 你写的:
for num in numbers:
    print(num)
 
# Python 在内部做的(概念上):
# 1. 调用 iter(numbers) 获取迭代器
# 2. 反复在迭代器上调用 next()
# 3. 当迭代器抛出 StopIteration 时停止

下面是显式写出来的样子:

python
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 items

for 循环会自动处理 StopIteration 异常,这就是为什么你在普通代码中从来不会看到它。

Yes

No -> StopIteration

for item in iterable:

Python 调用 iter(iterable)

获取迭代器对象

Python 调用 next(iterator)

还有元素吗?

赋值给 item

执行循环体

退出循环

35.2.3) 文件对象作为迭代器

文件对象是迭代器的极佳示例。当你遍历一个文件时,它会一次读取一行:

python
# 创建一个示例文件
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() 时,它们会返回自身:

python
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 对象是可迭代对象,会按需生成数字:

python
# 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: 3

range 之所以节省内存,是因为它不会把所有数字都存到内存里——它会在请求时计算每个数字:

python
# 这个 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 values

35.2.5) 字典迭代器

字典为键、值以及键值对提供了不同的迭代器:

python
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) 迭代器是可耗尽的

迭代器的一个关键特性是它们只能使用一次。一旦耗尽,它们不会重置:

python
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)

这不同于可迭代对象本身,可迭代对象可以被多次遍历:

python
numbers = [1, 2, 3]
 
# 第一次迭代
for num in numbers:
    print(num)  # Output: 1, 2, 3
 
# 第二次迭代(没问题 - 会创建一个新的迭代器)
for num in numbers:
    print(num)  # Output: 1, 2, 3

35.3) 使用 iter() 和 next() 逐步遍历可迭代对象

35.3.1) iter() 函数

iter() 函数接受一个可迭代对象并返回一个迭代器。这是迭代协议的第一步:

python
# 从不同的可迭代对象创建迭代器
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

python
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 colors

35.3.3) 给 next() 提供默认值

你可以给 next() 提供一个默认值作为第二个参数。当迭代器耗尽时,next() 不会抛出 StopIteration 异常,而是返回你指定的默认值:

python
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) 使用 iternext 创建自定义迭代器

35.4.1) 为什么要创建自定义迭代器

Python 的内置可迭代对象(列表、字符串、文件)覆盖了大多数常见场景。不过,有时你需要为特定行为创建自己的可迭代对象:

  • 使用自定义逻辑生成序列
  • 遍历你设计的数据结构
  • 以节省内存的方式迭代大型数据集
  • 实现惰性求值(lazy evaluation)(仅在需要时才计算值)

创建自定义迭代器需要实现两个特殊方法:__iter__()__next__()

35.4.2) 迭代器协议

要让一个对象成为迭代器,它必须实现:

  1. __iter__():返回迭代器对象本身(通常是 self
  2. __next__():返回序列中的下一个值,或在完成时抛出 StopIteration
python
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

让我们分解一下发生了什么:

  1. for 循环调用 iter(counter),这会调用 counter.__iter__() 并返回 counter 本身
  2. 循环反复调用 next(counter),这会调用 counter.__next__()
  3. 每次调用 __next__() 都会返回下一个数字,并递增 current
  4. current > end 时,__next__() 抛出 StopIteration,循环停止

35.4.3) 手动使用自定义迭代器

你也可以用 iter()next() 手动使用自定义迭代器:

python
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 exhausted

35.4.4) 迭代器是可耗尽的(再次强调)

记住,迭代器只能使用一次:

python
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

要再次迭代,你需要创建一个新实例:

python
# 每次迭代都创建一个新的 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) 创建一个可迭代类(不仅仅是迭代器)

很多时候,你希望一个类是可迭代的,并且每次都创建一个全新的迭代器。为此,将可迭代对象与迭代器分离:

python
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) 实用示例:遍历自定义数据结构

让我们为一个自定义数据结构创建迭代器——一个简单的播放列表:

python
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 California

35.4.7) 何时使用自定义迭代器

在以下情况下创建自定义迭代器:

  1. 你需要惰性求值(lazy evaluation):按需生成值,而不是一次性存储全部
  2. 你有自定义数据结构:让它可迭代,从而能够与 for 循环一起工作
  3. 你需要特殊的迭代逻辑:跳过元素、转换值,或实现复杂的步进规则
  4. 内存效率很重要:生成大型序列而不存储它们

不过,在第 36 章中,你将学习生成器(generators),它通过 yield 关键字提供了一种更简单的方式来创建迭代器。与手动实现 __iter__()__next__() 相比,生成器通常更受欢迎,因为它更简洁也更易理解。

理解如何创建自定义迭代器能让你洞察 Python 的迭代协议是如何工作的,即使你通常会使用生成器来替代它。在这里学到的概念——__iter__()__next__()StopIteration——是理解生成器和下一章其他高级迭代技术的基础。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai