云计算百科
云计算领域专业知识百科平台

Python语言基础:17_迭代器与生成器

这两个概念是 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. 什么是迭代器?

迭代器是一个可以记住遍历位置、能一个一个往外吐元素的对象。 通俗理解:可迭代对象是「一整盒乒乓球」,迭代器就是「夹乒乓球的夹子」—— 它会记住你夹到第几个了,每次调用就夹出下一个,夹完就没有了。

迭代器必须满足两个条件:

  • 有 __iter__() 方法:返回迭代器自身
  • 有 __next__() 方法:返回下一个元素,没有元素了就抛出 StopIteration 异常
  • 💡 结论:迭代器一定是可迭代对象,但可迭代对象不一定是迭代器。比如列表是可迭代对象,但不是迭代器。


    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 循环,本质上就是自动帮你做了这三件事:

  • 调用 iter(可迭代对象) 拿到迭代器
  • 循环调用 next(迭代器) 取下一个元素
  • 遇到 StopIteration 异常,自动结束循环
  • 我们手动模拟 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 有两种创建生成器的方式:

  • 生成器表达式:用小括号,写法类似推导式
  • 生成器函数:用 def + yield 关键字(最常用、最强大)

  • 方式 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 核心区别
    对比项returnyield
    作用 结束整个函数,返回值 暂停函数,返回值,下次继续
    次数 一个函数只能执行一次 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() 属于进阶用法,后期再深入。


    四、三者关系总结

    可迭代对象(范围最大)
    └── 迭代器
    └── 生成器(特殊的迭代器)

  • 可迭代对象:实现了 __iter__ 方法,能被 for 遍历
  • 迭代器:同时实现了 __iter__ 和 __next__ 方法,惰性取值,只能遍历一次
  • 生成器:用生成器表达式或 yield 函数创建的迭代器,写法更简洁

  • 五、新手高频易错点总结

  • 可迭代对象 ≠ 迭代器 列表、字符串是可迭代对象,但不是迭代器;用 iter() 转换后才是迭代器。
  • 迭代器 / 生成器只能遍历一次 遍历完就空了,想再次遍历必须重新创建一个新的迭代器 / 生成器。
  • 调用生成器函数不会执行代码 只是返回生成器对象,必须调用 next() 才会真正开始执行函数代码。
  • yield 和 return 搞混 return 是结束函数;yield 是暂停函数,下次继续。一个生成器可以有多个 yield。
  • 小括号不是元组推导式(i for i in range(5)) 是生成器表达式,不是元组推导式,Python 没有元组推导式。
  • 什么时候用生成器?
    • 数据量特别大,不想一次性占满内存
    • 不需要一次性拿到所有数据,用一个取一个
    • 数据流处理、大文件读取、无限序列生成
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » Python语言基础:17_迭代器与生成器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!