Python 迭代器与可迭代对象详解

在 Python 开发中,「遍历」是最常见的操作之一——不管是扫列表、查字典键值对,还是逐行读文件,我们都习惯直接写 for ... in ...。但你有没有好奇过:为什么这些东西能放进 for 循环?为什么有些对象能被 next() 直接取元素? 这背后的核心,就是「可迭代对象(Iterable)」和「迭代器(Iterator)」。

可迭代对象 (Iterable)

简单来说,可迭代对象是“能被 for 循环遍历的对象”——它本身不直接产生“下一个值”的能力,但可以“变出”能产生值的工具。

Python 里常见的可迭代对象分两类:

  • 基础集合类:listtupledictsetstrbytes
  • 带延迟特性的类:生成器(generator)、文件对象、zip()/map()/filter() 的返回值等

怎么判断是不是可迭代?

isinstance() 配合 collections.abc.Iterable(注意要先导入标准库的抽象基类):

from collections.abc import Iterable

# 测试基础集合
print(isinstance([1, 2], Iterable))  # True
print(isinstance({'a': 1}, Iterable))  # True
print(isinstance('hello', Iterable))  # True

# 测试延迟对象
print(isinstance((x for x in range(3)), Iterable))  # True
print(isinstance(open('test.txt', 'r'), Iterable))  # True(注意别忘关文件!)

# 测试非可迭代
print(isinstance(100, Iterable))  # False
print(isinstance(True, Iterable))  # False

迭代器 (Iterator)

迭代器是可迭代对象的“进阶版”——它不仅能进 for 循环,还能被 next() 主动调用,直到耗尽抛出 StopIteration 异常,是真正“手握数据流控制权”的工具。

怎么判断是不是迭代器?

同样用 isinstance() + 抽象基类 collections.abc.Iterator

from collections.abc import Iterator

# 生成器是迭代器!
print(isinstance((x for x in range(3)), Iterator))  # True
print(isinstance(iter([]), Iterator))  # True(用 iter() 转的)

# 基础集合不是迭代器!
print(isinstance([1, 2], Iterator))  # False
print(isinstance('hello', Iterator))  # False

可迭代转迭代器

用内置函数 iter() 就能一键转换:

nums = [1, 2, 3]
it_nums = iter(nums)  # 拿到迭代器

print(next(it_nums))  # 1
print(next(it_nums))  # 2
print(next(it_nums))  # 3
print(next(it_nums))  # 报错 StopIteration!

迭代器 vs 可迭代对象:到底差在哪?

直接看文字可能绕,我们用对比表格+场景例子讲透:

核心特性可迭代对象(Iterable)迭代器(Iterator)
能不能进 for 循环
能不能用 next() 主动取
是不是“惰性计算”大部分不是(除了特殊的)✅ 绝对是!
能不能表示无限序列❌(内存装不下)
内存占用固定(预存所有元素)极低(只存当前逻辑)

举个最直观的无限自然数例子:

  • 用列表写?不可能——[0,1,2,...] 写到内存爆炸也写不完
  • 用迭代器写?分分钟搞定(后面讲自定义迭代器会实现)

再举个内存对比的小例子:

# 生成 100万 个平方数的列表——预占约 8MB 内存(每个 int 在 CPython 里大概占 28B,但列表会优化)
big_list = [x*x for x in range(1_000_000)]

# 生成 100万 个平方数的迭代器——只占几十字节!
big_iterator = (x*x for x in range(1_000_000))

for 循环的“真面目”

你写的每一行 for x in xxx,Python 底层都会自动做这三件事:

  1. 调用 iter(xxx) 拿到迭代器对象
  2. 循环调用 next(迭代器) 赋值给 x
  3. 遇到 StopIteration 就优雅退出

比如这段简单的代码:

for num in [1, 2, 3]:
    print(num)

Python 实际上在执行这段:

# 第一步:取迭代器
it = iter([1, 2, 3])

# 第二步第三步:循环+异常处理
while True:
    try:
        num = next(it)
        print(num)
    except StopIteration:
        break

是不是突然觉得 for 循环没那么“神秘”了?

实用开发建议

1. 处理大数据/无限序列,首选迭代器/生成器

如果要处理几百万、几千万行的文件,或者遍历无限序列,别用列表推导式,用生成器表达式或者迭代器类——不然内存会被撑爆。

2. 自定义迭代器,只需要两个方法

如果要自己写迭代器类(比如实现刚才的无限自然数),必须实现:

  • __iter__()必须返回自己(self,这是迭代器和可迭代对象的核心接口统一
  • __next__()实现“取元素、推进指针、抛异常”的逻辑

直接上代码:

from collections.abc import Iterator

class InfiniteNaturals(Iterator):
    def __init__(self, start=0):
        self.current = start
    
    # 必须返回 self!
    def __iter__(self):
        return self
    
    # 实现取元素逻辑
    def __next__(self):
        # 这里没写终止条件——真·无限!
        result = self.current
        self.current += 1
        return result

# 测试
nat = InfiniteNaturals(10)
print(next(nat))  # 10
print(next(nat))  # 11
# 别写 for nat in nat 哦——会无限循环!

如果要做有终止条件的自定义迭代器,加个判断抛 StopIteration 就行:

class RangeCustom(Iterator):
    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
        result = self.current
        self.current += 1
        return result

# 测试
for num in RangeCustom(2, 5):
    print(num)  # 2, 3, 4

3. 迭代器是“一次性”的!

这个点非常容易踩坑!迭代器遍历完一次就“空”了,再用 next() 或者 for 循环都不会有值:

it = iter([1, 2, 3])
print(list(it))  # [1,2,3]
print(list(it))  # []!!!空的!

总结

理解迭代器和可迭代对象,是写高效、Pythonic、内存友好代码的基础:

  • 可迭代对象是“容器/原料”,能提供迭代器
  • 迭代器是“工具/工人”,手握惰性计算的逻辑
  • for 循环只是迭代器的语法糖
  • 自定义迭代器要实现 __iter__(返回自己)和 __next__

下次再写遍历的时候,不妨停下来想想:这里用列表还是生成器/迭代器更合适?