这两个概念是 Python 进阶的核心知识点,也是新手最容易懵的地方。全程用白话 + 可运行示例,从最基础的「可迭代对象」讲起,一步步拆解原理、用法和区别,你可以把代码复制到 Python 里边运行边理解。
一、前置铺垫:先搞懂「可迭代对象」
我们平时用 for 循环遍历列表、字符串、字典、元组,这些能被 for 循环逐个取出元素的东西,都叫「可迭代对象(Iterable)」。
通俗理解:可迭代对象就是「一整盒乒乓球」,里面装了很多元素,你可以按顺序一个一个拿出来用。
1. 常见的可迭代对象
- 序列类型:列表 list、元组 tuple、字符串 str
- 集合类型:字典 dict、集合 set
- 特殊的:range() 生成的数字序列、文件对象
2. 怎么判断是不是可迭代对象
可以用 collections.abc 模块里的 Iterable 来判断:
from collections.abc import Iterable
# 列表是可迭代对象
print(isinstance([1,2,3], Iterable)) # True
# 字符串是可迭代对象
print(isinstance("hello", Iterable)) # True
# 普通数字不是
print(isinstance(100, Iterable)) # False
3. 可迭代对象的本质
只要一个对象实现了 __iter__() 方法,它就是可迭代对象。for 循环遍历它的时候,会先调用这个方法,拿到一个「迭代器」,再通过迭代器逐个取元素。
二、迭代器(Iterator)
1. 什么是迭代器?
迭代器是一个可以记住遍历位置、能一个一个往外吐元素的对象。 通俗理解:可迭代对象是「一整盒乒乓球」,迭代器就是「夹乒乓球的夹子」—— 它会记住你夹到第几个了,每次调用就夹出下一个,夹完就没有了。
迭代器必须满足两个条件:
💡 结论:迭代器一定是可迭代对象,但可迭代对象不一定是迭代器。比如列表是可迭代对象,但不是迭代器。
2. 两个核心内置函数:iter () 和 next ()
我们不用直接调用魔法方法,Python 提供了两个简化函数:
- iter(可迭代对象):把可迭代对象转换成迭代器
- next(迭代器):取出迭代器的下一个元素
完整示例:手动遍历迭代器
# 1. 有一个可迭代对象:列表
lst = ["苹果", "香蕉", "橙子"]
# 2. 用 iter() 把它转成迭代器
it = iter(lst)
print(type(it)) # <class 'list_iterator'>
# 3. 用 next() 一个一个取元素
print(next(it)) # 苹果
print(next(it)) # 香蕉
print(next(it)) # 橙子
# 4. 元素取完了,再调用 next() 就会报错
print(next(it)) # 抛出 StopIteration 异常
运行到最后一行会报错:
StopIteration
这是正常现象 —— 迭代器遍历完就空了,没有下一个元素了。
3. for 循环的底层本质(重点理解)
你天天写的 for 循环,本质上就是自动帮你做了这三件事:
我们手动模拟 for 循环:
lst = [10, 20, 30]
# 下面这段代码,等价于 for num in lst: print(num)
it = iter(lst)
while True:
try:
num = next(it)
print(num)
except StopIteration:
# 没有元素了,跳出循环
break
运行结果和普通 for 循环完全一样:
10
20
30
理解了这个,你就彻底懂了 for 循环的工作原理。
4. 迭代器的核心特点
验证:迭代器只能遍历一次
it = iter([1,2,3])
# 第一次遍历
print("第一次遍历:")
for i in it:
print(i)
# 第二次遍历:什么都没有,因为已经取完了
print("第二次遍历:")
for i in it:
print(i)
输出:
第一次遍历:
1
2
3
第二次遍历:
第二次循环什么都没打印,因为迭代器已经空了。
三、生成器(Generator)—— 更简单的迭代器
迭代器虽然好用,但自己写一个迭代器要定义类、实现 __iter__ 和 __next__ 方法,非常麻烦。 Python 提供了更简洁的方式来创建迭代器 ——生成器。
核心结论:生成器是一种特殊的迭代器,它自带迭代器的所有特性(惰性、省内存、只能遍历一次),但写起来极其简单。
Python 有两种创建生成器的方式:
方式 1:生成器表达式
写法和列表推导式几乎一模一样,只是把方括号 [] 换成小括号 ()。
语法格式
(表达式 for 变量 in 可迭代对象 [if 条件])
对比:列表推导式 vs 生成器表达式
# 列表推导式:一次性生成所有元素,全部放进内存
lst = [i**2 for i in range(5)]
print(lst) # [0, 1, 4, 9, 16]
print(type(lst)) # <class 'list'>
# 生成器表达式:不生成元素,只保存生成规则
gen = (i**2 for i in range(5))
print(gen) # <generator object <genexpr> at 0x…>
print(type(gen)) # <class 'generator'>
列表打印出来是完整的元素列表,生成器打印出来是一个对象 —— 它没有把所有元素算好存起来,只保存了生成规则,你要一个它才算一个。
生成器的使用
和迭代器一样,用 next() 逐个取,或者用 for 循环遍历:
gen = (i**2 for i in range(3))
# 手动取
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 4
# 更常用的是直接 for 遍历
gen2 = (i*2 for i in range(5))
for num in gen2:
print(num)
核心优势:极致节省内存
如果要生成 1000 万个数字,列表会一次性把 1000 万个数字全部放进内存,可能直接占几百 MB;而生成器几乎不占内存,因为它根本没生成元素,只保存了规则。
# 几乎不占内存
gen = (i for i in range(10000000))
# 占大量内存
lst = [i for i in range(10000000)]
处理超大文件、海量数据时,生成器是必备工具。
方式 2:生成器函数(yield 关键字,重点 + 难点)
普通函数用 return 返回值,返回一次函数就结束了; 生成器函数用 yield 返回值,返回之后函数会暂停执行,下次调用再从暂停的地方继续往下走。
这是生成器最核心的特性,也是新手最难理解的地方,我们一步步拆解。
1. 基础语法
def 生成器函数名():
…
yield 值
…
yield 值
- 函数里只要有 yield 关键字,它就不再是普通函数,而是生成器函数
- 调用生成器函数,不会执行函数代码,只会返回一个生成器对象
- 必须调用 next() 才会开始执行代码,遇到 yield 就暂停,返回值
2. 最简单的生成器函数 + 逐行执行过程
我们写一个生成 1、2、3 的生成器,一步步看它是怎么运行的:
def my_generator():
print("— 开始执行第1段代码 —")
yield 1 # 第1个暂停点
print("— 继续执行第2段代码 —")
yield 2 # 第2个暂停点
print("— 继续执行第3段代码 —")
yield 3 # 第3个暂停点
# 调用函数:不会执行任何代码,只返回生成器对象
gen = my_generator()
print(gen) # <generator object my_generator at 0x…>
现在我们调用三次 next(),一步步看执行过程:
第一次调用 next (gen)
- 函数开始执行,打印 — 开始执行第1段代码 —
- 遇到第一个 yield 1,暂停函数,把 1 返回出去
print(next(gen))
# 输出:
# — 开始执行第1段代码 —
# 1
第二次调用 next (gen)
- 从上次暂停的地方(第一个 yield 后面)继续往下执行
- 打印 — 继续执行第2段代码 —
- 遇到第二个 yield 2,再次暂停,返回 2
print(next(gen))
# 输出:
# — 继续执行第2段代码 —
# 2
第三次调用 next (gen)
- 从第二个 yield 后面继续执行
- 打印 — 继续执行第3段代码 —
- 遇到第三个 yield 3,暂停,返回 3
print(next(gen))
# 输出:
# — 继续执行第3段代码 —
# 3
第四次调用 next (gen)
- 从第三个 yield 后面继续执行,函数里没有代码了
- 抛出 StopIteration 异常
print(next(gen)) # StopIteration
🔑 核心结论:yield 的作用是「返回值 + 暂停函数」,下次 next() 从暂停位置继续执行。这是它和 return 最本质的区别。
3. yield vs return 核心区别
| 作用 | 结束整个函数,返回值 | 暂停函数,返回值,下次继续 |
| 次数 | 一个函数只能执行一次 return | 可以有多个 yield,多次暂停返回 |
| 函数状态 | 执行完就销毁所有局部变量 | 暂停时保留所有局部变量的状态 |
| 所属 | 普通函数 | 生成器函数 |
4. 经典实战例子:生成斐波那契数列
斐波那契数列:1, 1, 2, 3, 5, 8, 13… 后一个数等于前两个数之和。
如果用普通列表实现,生成前 N 个数,会把所有数都存在内存里,N 很大时非常占内存:
def fib_list(n):
a, b = 0, 1
res = []
for _ in range(n):
res.append(b)
a, b = b, a+b
return res
print(fib_list(10))
# [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
用生成器实现,惰性计算,要一个算一个,几乎不占内存:
def fib_gen(n):
a, b = 0, 1
for _ in range(n):
yield b # 遇到yield就返回当前值,暂停
a, b = b, a + b
# 遍历生成器
for num in fib_gen(10):
print(num, end=" ")
# 输出:1 1 2 3 5 8 13 21 34 55
哪怕生成 100 万个斐波那契数,生成器版本的内存占用也几乎不变,这就是它最大的价值。
5. 生成器的进阶方法(了解即可)
生成器除了 next(),还有两个常用方法:
- send(值):给生成器内部传值,同时唤醒暂停的生成器
- close():手动关闭生成器
send () 简单示例
def gen():
print("启动")
x = yield 1
print(f"收到了:{x}")
y = yield 2
print(f"收到了:{y}")
g = gen()
# 第一次必须用 next() 启动,或者 send(None)
print(next(g)) # 启动,打印"启动",返回1
# send传值进去,赋值给x,继续执行到下一个yield
print(g.send("hello")) # 打印"收到了:hello",返回2
print(g.send("world")) # 打印"收到了:world",没有yield了,抛异常
💡 新手提示:前期先掌握 next() 和 for 遍历就行,send() 属于进阶用法,后期再深入。
四、三者关系总结
可迭代对象(范围最大)
└── 迭代器
└── 生成器(特殊的迭代器)
五、新手高频易错点总结
- 数据量特别大,不想一次性占满内存
- 不需要一次性拿到所有数据,用一个取一个
- 数据流处理、大文件读取、无限序列生成
网硕互联帮助中心



评论前必须登录!
注册