Python 迭代器与生成器的具体使用

 更新时间:2026年06月07日 09:15:45   作者:copyer_xyf  
本文主要介绍了Python 迭代器与生成器的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

本文面向已有前端开发基础、正在学习 Python 的开发者。

迭代器和生成器解决的是同一个问题:数据不一定要一次性全部准备好,可以在需要的时候一个一个取出来。前端里最接近的经验是 for...ofSymbol.iterator、生成器函数 function*yield

这几个概念可以先合在一起记:

  • 可迭代对象表示“可以被遍历的数据源”
  • 迭代器表示“真正负责一步一步取值的对象”
  • 生成器表示“用 yield 快速创建出来的迭代器”
    后面的 for 循环,本质上就是先从可迭代对象拿到迭代器,再不断从迭代器里取下一个值。

一、先把概念边界讲清楚

先记住一条主线:

for item in obj
  -> 先调用 iter(obj) 拿到迭代器
  -> 再不断调用 next(迭代器)
  -> 遇到 StopIteration 后结束

所以这几个概念可以这样分:

概念关注点PythonJavaScript
可迭代对象能不能开始遍历能被 iter() 接受有 Symbol.iterator
迭代器这次遍历走到哪里了能被 next() 调用有 next()
迭代协议遍历接口怎么约定iter() -> next() -> 结束时抛异常Symbol.iterator -> next()
生成器怎么快速创建迭代器函数体里写 yieldfunction* + yield

两门语言只是接口名字和结束方式不同:

Python:
可迭代对象 -- iter() --> 迭代器 -- next() --> value / StopIteration
JS:
可迭代对象 -- Symbol.iterator() --> 迭代器 -- next() --> { value, done }

最关键的边界是:可迭代对象表示“能开始一次遍历”,迭代器表示“这次遍历本身”。列表、字符串这类可迭代对象可以反复遍历,因为每次都能创建新的迭代器;已经创建出来的迭代器通常只能向前走,取过的值不会自动回到起点。

生成器不算新的遍历体系,它只是更省事的迭代器写法。手写迭代器要自己维护位置和结束条件;生成器用 yield 保存暂停点,每次 next() 都从上一次暂停的位置继续执行。

是不是有点懵😳

二、从 for 循环看迭代过程

JavaScript 里,一个对象只要实现了 Symbol.iterator,就可以被 for...of 消费。

const names = ["张三", "李四", "王五"];

for (const name of names) {
  console.log(name);
}

如果拆开看,for...of 背后大概做了这些事:

const iterator = names[Symbol.iterator]();

console.log(iterator.next()); // { value: '张三', done: false }
console.log(iterator.next()); // { value: '李四', done: false }
console.log(iterator.next()); // { value: '王五', done: false }
console.log(iterator.next()); // { value: undefined, done: true }

所以前端里有两层概念:

概念判断方式作用
iterable有 Symbol.iterator可以交给 for...of
iterator有 next()可以一步一步取值

Python 也有这两层,只是名字和结束方式不同:

JavaScriptPython
obj[Symbol.iterator]()iter(obj)
iterator.next()next(iterator)
返回 { value, done }返回本次值
done: true 表示结束抛出 StopIteration 表示结束

把这个对照关系记住,后面的 Python 语法就会清楚很多。

三、可迭代对象 iterable

可迭代对象就是:能被 for 循环遍历的对象。

names = ['张三', '李四', '王五']
cities = ('北京', '上海', '深圳')
msg = 'hello'

for name in names:
    print(name)

这些对象都能被 for 遍历,所以它们都是可迭代对象。

从协议角度看,可迭代对象要能被 iter() 接受:

names = ['张三', '李四', '王五']
msg = 'hello'
age = 18

print(iter(names)) # list_iterator
print(iter(msg))   # str_iterator

# print(iter(age)) # TypeError: 'int' object is not iterable

也可以用 hasattr 粗略观察:

names = ['张三', '李四', '王五']
msg = 'hello'
age = 18

print(hasattr(names, '__iter__')) # True
print(hasattr(msg, '__iter__'))   # True
print(hasattr(age, '__iter__'))   # False

这里的 __iter__ 是 Python 的魔法方法。平时开发一般不直接写 names.__iter__(),而是用内置函数 iter(names)

obj.__iter__()
  -> 底层魔法方法

iter(obj)
  -> 日常使用方式
  -> 内部会调用 obj.__iter__()

四、迭代器 iterator

调用 iter(可迭代对象) 之后,会得到一个迭代器。

names = ['张三', '李四', '王五']

it = iter(names)

print(next(it)) # 张三
print(next(it)) # 李四
print(next(it)) # 王五
print(next(it)) # StopIteration

迭代器的核心能力是:记住当前取到哪里了,每次 next() 返回下一个值。

也就是说,迭代器内部有状态,类似一个指针:

初始位置
  -> next() 取第 1 个
  -> next() 取第 2 个
  -> next() 取第 3 个
  -> 没有数据了,抛 StopIteration

如果用 while 手动模拟 for,大概是这样:

names = ['张三', '李四', '王五']

it = iter(names)

while True:
    try:
        item = next(it)
        print(item)
    except StopIteration:
        break

所以 for item in names 并不神秘,它背后就是:

先调用 iter(names) 得到迭代器
再不断调用 next(迭代器)
遇到 StopIteration 后结束循环

迭代器自己也是可迭代对象

迭代器一般也有 __iter__ 方法,并且返回自己。

names = ['张三', '李四', '王五']

it = iter(names)

print(iter(it) is it) # True

这样设计的原因是:for 循环第一步一定会调用 iter(x)。如果传进去的已经是迭代器,iter(迭代器) 必须也能正常工作。

迭代器会被消耗

迭代器不是列表,它是一次性向前取值的过程。

names = ['张三', '李四', '王五']

it = iter(names)

print(next(it)) # 张三

for name in it:
    print(name)

# 只会继续输出:
# 李四
# 王五

前面已经被 next(it) 取走的值,不会在后面的 for 里重新出现。

这点很像前端里已经调用过几次 iterator.next() 后,再继续 for...of 或继续 .next(),状态会接着往后走,而不是自动重置。

五、自定义可迭代对象

如果希望自己的类能被 for 遍历,就要实现迭代器协议。

需求:让 Person 实例可以被遍历,依次取出姓名、年龄、性别、地址。

p1 = Person('张三', 18, '男', '北京昌平')

for item in p1:
    print(item)

写法一:对象和迭代器分开

这种写法最清晰:Person 负责保存业务数据,PersonIterator 负责遍历过程。

class Person:
    def __init__(self, name, age, gender, address):
        self.name = name
        self.age = age
        self.gender = gender
        self.address = address

    def __iter__(self):
        # 返回一个专门负责遍历 Person 的迭代器
        return PersonIterator(self)


class PersonIterator:
    def __init__(self, person):
        # 保存外部传进来的 Person 对象
        self.person = person
        # 记录当前取到哪个位置
        self.index = 0
        # 配置要遍历哪些字段
        self.attrs = [
            person.name,
            person.age,
            person.gender,
            person.address,
        ]

    def __iter__(self):
        # 迭代器的 __iter__ 返回自己
        return self

    def __next__(self):
        if self.index >= len(self.attrs):
            raise StopIteration

        value = self.attrs[self.index]
        self.index += 1
        return value

执行:

p1 = Person('张三', 18, '男', '北京昌平')

for item in p1:
    print(item)

输出:

张三
18

北京昌平

这个写法适合业务对象比较复杂的场景。业务对象和遍历状态分开,Person 不需要关心当前遍历到第几个字段。

写法二:对象自己也是迭代器

也可以让 Person 同时实现 __iter____next__

class Person:
    def __init__(self, name, age, gender, address):
        self.name = name
        self.age = age
        self.gender = gender
        self.address = address
        self.attrs = [name, age, gender, address]

    def __iter__(self):
        self.index = 0
        return self

    def __next__(self):
        if self.index >= len(self.attrs):
            raise StopIteration

        value = self.attrs[self.index]
        self.index += 1
        return value

这种写法代码更少,但要注意:遍历状态放在对象自己身上。多个地方同时遍历同一个对象时,更容易相互影响。

学习阶段可以先写这种,真实业务里更推荐“对象和迭代器分开”,职责更清楚。

六、为什么需要迭代器

迭代器最大的价值是惰性计算:不一次性生成所有结果,而是在需要时才计算下一个。

比如生成斐波那契数列,如果一次性生成 100000 个数字并放进列表,内存会越来越大。

def fib_list(total):
    result = []
    a = 0
    b = 1

    for _ in range(total):
        result.append(a)
        a, b = b, a + b

    return result

如果改成迭代器,每次只返回当前这个数:

class Fibo:
    def __init__(self, total):
        self.total = total
        self.index = 0
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= self.total:
            raise StopIteration

        value = self.a
        self.a, self.b = self.b, self.a + self.b
        self.index += 1
        return value

使用:

for number in Fibo(10):
    print(number)

迭代器适合这些场景:

  • 数据量很大,不想一次性放进内存
  • 不确定用户最终会消费多少结果
  • 数据来自文件、网络、数据库游标这类流式来源
  • 每个结果只依赖当前状态和上一个状态

七、生成器 generator

生成器可以理解成:Python 帮你自动实现迭代器协议的语法糖。

只要一个函数体里出现 yield,这个函数就不是普通函数,而是生成器函数。

def demo():
    print('demo 函数开始执行了')
    print(100)
    yield '我是第 1 个 yield 返回的数据'

    a = 200
    print(a)
    yield '我是第 2 个 yield 返回的数据'

    b = 300
    print(b)
    return '执行结束'

调用生成器函数时,函数体不会立刻执行,而是返回一个生成器对象。

d = demo()

print(hasattr(d, '__iter__')) # True
print(hasattr(d, '__next__')) # True

生成器对象本质上是一种迭代器,所以可以用 next() 取值:

d = demo()

print(next(d))
print(next(d))

try:
    print(next(d))
except StopIteration as e:
    print(e.value) # 执行结束

执行过程可以这样理解:

第一次 next()
  -> 函数从开头执行
  -> 遇到第一个 yield 暂停
  -> yield 后面的值作为本次 next() 的返回值

第二次 next()
  -> 从上次暂停的位置继续执行
  -> 遇到第二个 yield 再暂停

第三次 next()
  -> 继续执行
  -> 遇到 return
  -> 抛 StopIteration
  -> return 后面的值会放到异常对象的 value 里

生成器和普通函数最大的差异是:普通函数一次调用跑到底,生成器函数可以在 yield 处暂停,下次再接着跑。

前端里可以对照 function*

function* demo() {
  console.log("demo 开始执行");
  yield "第 1 个值";
  yield "第 2 个值";
}

const d = demo();

console.log(d.next());
console.log(d.next());
console.log(d.next());

八、yield 的几个常见写法

yield 写在循环里

最常见的生成器写法,是在循环里不断 yield

def fib(total):
    a = 0
    b = 1

    for _ in range(total):
        yield a
        a, b = b, a + b

使用:

for number in fib(10):
    print(number)

这比手写 class Fibo 简洁很多,但效果类似:每次需要下一个值时,才继续往后计算。

yield from

yield from 可以把另一个可迭代对象里的值依次产出。

def demo():
    nums = [10, 20, 30, 40]
    yield from nums

它大致等价于:

def demo():
    nums = [10, 20, 30, 40]

    for num in nums:
        yield num

所以 yield from 可以记成:

把某个可迭代对象里的数据,一个一个 yield 出去

send()

生成器除了能往外吐值,也能在继续执行时接收外部传进来的值。

def demo():
    print('demo 函数开始执行了')

    a = yield '第 1 个 yield 的返回值'
    print(f'a 接收到:{a}')

    b = yield '第 2 个 yield 的返回值'
    print(f'b 接收到:{b}')

使用:

d = demo()

print(next(d))        # 先启动生成器,停在第一个 yield
print(d.send('张三')) # 把 '张三' 传给变量 a,然后继续执行

try:
    d.send('李四')     # 把 '李四' 传给变量 b,然后继续执行到函数结束
except StopIteration:
    print('生成器执行结束')

注意:第一次启动生成器时不能直接传普通值,因为代码还没有运行到任何一个 yield 位置,没有地方接收这个值。

d = demo()

# d.send('张三') # TypeError
d.send(None)    # 等价于 next(d)

next() 只能取值;send(value) 既能让生成器继续执行,也能把值传回上一次暂停的 yield 表达式。

九、生成器表达式

生成器表达式是一种快速创建生成器对象的写法,长得很像列表推导式。

nums = [10, 20, 30, 40]

result1 = [n * 2 for n in nums]
result2 = (n * 2 for n in nums)

print(result1) # [20, 40, 60, 80]
print(result2) # <generator object ...>

区别在于:

写法结果是否立刻生成全部结果
[n * 2 for n in nums]列表
(n * 2 for n in nums)生成器对象

生成器表达式适合“每个结果只依赖当前元素”的场景。

nums = [10, 20, 30, 40]

result = (n * 2 for n in nums)

for item in result:
    print(item)

它不会一次性创建 [20, 40, 60, 80],而是每次循环时才计算当前这个 item

如果数据量很小,并且后面要反复使用结果,列表推导式更直观。如果数据量很大,只需要顺序消费一遍,生成器表达式更省内存。

十、最后怎么选

可以按这个顺序判断:

只是遍历已有 list / tuple / dict / str
  -> 直接 for

想让自己的类能被 for
  -> 实现 __iter__
  -> 如果要自己控制取值过程,再实现 __next__

要一个一个惰性产出结果
  -> 优先写生成器函数 yield

只是把一个可迭代对象映射成另一个惰性结果
  -> 用生成器表达式

需要复杂状态、多个方法、可维护的对象封装
  -> 手写迭代器类

最容易混淆的点:

问题结论
能 for 的一定是迭代器吗不一定,可能只是可迭代对象
迭代器能 for 吗能,因为迭代器的 __iter__ 返回自己
iter(obj) 做了什么调用 obj.__iter__(),拿到迭代器
next(it) 做了什么调用 it.__next__(),拿下一个值
取完后怎么结束Python 抛 StopIteration
生成器是什么用 yield 自动创建出来的迭代器
生成器会立刻执行函数体吗不会,第一次 next() 才开始执行
迭代器能重复遍历吗通常不能,它会被消耗

到此这篇关于Python 迭代器与生成器的具体使用的文章就介绍到这了,更多相关Python 迭代器与生成器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python解析不规则JSON数据的实战技巧

    Python解析不规则JSON数据的实战技巧

    在真实世界的数据处理中,我们很少遇到教科书式的标准 JSON,文将为你提供一套从温和修复到强力解析的完整工具箱,用 Python 轻松驯服这些野性数据,有需要的小伙伴可以了解下
    2026-01-01
  • Python从Excel读取数据并使用Matplotlib绘制成二维图像

    Python从Excel读取数据并使用Matplotlib绘制成二维图像

    本课程实现使用 Python 从 Excel 读取数据,并使用 Matplotlib 绘制成二维图像。这一过程中,将通过一系列操作来美化图像,最终得到一个可以出版级别的图像。本课程对于需要书写实验报告,学位论文,发表文章,做报告的学员具有较大价值
    2023-02-02
  • Python自定义简单图轴简单实例

    Python自定义简单图轴简单实例

    这篇文章主要介绍了Python自定义简单图轴简单实例,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Python使用pandasai实现数据分析

    Python使用pandasai实现数据分析

    本文主要介绍了Python使用pandasai实现数据分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • 使用Python构建一个完整的实时数据处理平台

    使用Python构建一个完整的实时数据处理平台

    在当今数据驱动的时代,实时数据处理能力已成为企业核心竞争力之一,本文将介绍如何使用Python技术栈构建一个完整的实时数据处理平台,涵盖从数据采集、处理、存储到可视化展示的全流程,需要的朋友可以参考下
    2026-02-02
  • python pandas库读取excel/csv中指定行或列数据

    python pandas库读取excel/csv中指定行或列数据

    通过阅读表格,可以发现Pandas中提供了非常丰富的数据读写方法,下面这篇文章主要给大家介绍了关于python利用pandas库读取excel/csv中指定行或列数据的相关资料,需要的朋友可以参考下
    2022-02-02
  • 解决TensorFlow程序无限制占用GPU的方法

    解决TensorFlow程序无限制占用GPU的方法

    这篇文章主要介绍了解决TensorFlow程序无限制占用GPU的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • Python进阶之协程详解

    Python进阶之协程详解

    这篇文章主要为大家介绍了Python进阶之协程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • Python +Selenium解决图片验证码登录或注册问题(推荐)

    Python +Selenium解决图片验证码登录或注册问题(推荐)

    这篇文章主要介绍了Python Selenium解决图片验证码登录或注册问题,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • Pandas数据结构中Series属性详解

    Pandas数据结构中Series属性详解

    本文主要介绍了Pandas数据结构中Series属性详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04

最新评论