Files
Cloud-book/Python/Python面向函数/迭代器与生成器.md
2025-08-27 17:10:05 +08:00

233 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 可迭代对象
## 迭代
使用 `for i in xxx` 对字符串、列表、元组、字典、集合进行循环取值的过程称为遍历,也叫做迭代。
```python
li = [1, 2, 3, 'a', 'b', 'c']
for i in li:
print(i)
str = "hello,world"
for j in str:
print(j)
dic = {'name': 'nls', 'age': 18, 'job': 'teacher'}
for k,v in dic.items():
print(k,v)
```
## 定义
可迭代对象是能够通过迭代逐一返回其元素的对象。它必须满足以下条件之一:
- 实现 `__iter__()` 方法,返回一个可迭代对象
- 实现 `__getitem__()` 方法支持从索引0开始的顺序访问
## 常见可迭代对象类型
## 判断是否为可迭代对象
```python
from collections.abc import Iterable
l = [1, 2, 3, 4]
t = (1, 2, 3, 4)
d = {1: 2, 3: 4}
s = {1, 2, 3, 4}
a = 100
print(isinstance(l, Iterable))
print(isinstance(t, Iterable))
print(isinstance(d, Iterable))
print(isinstance(s, Iterable))
print(isinstance(a, Iterable))
```
# 迭代器
迭代器是一种用于遍历可迭代对象的对象。它可以记录遍历的位置,
可迭代对象可以通过 `__iter__` 方法返回一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据。
上面讲到迭代是访问集合元素的一种方式。而迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,迭代器只能往前不会后退。
```python
l = [1, 2, 3, 4]
t = (1, 2, 3, 4)
d = {1: 2, 3: 4}
s = {1, 2, 3, 4}
print(dir(l))
print(dir(t))
print(dir(d))
print(dir(s))
```
## 迭代器的本质
我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在 `for...in...` 中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为**迭代器(Iterator)**。
## 迭代器协议
迭代器遵循迭代协议,内部主要定义了 `__iter__()``__next__()` 两个方法
- `__iter__()` 方法用于初始化一个迭代器,返回迭代器本身
- `__next__()` 方法用于迭代下一个数据。当没有元素可返回时,抛出 `StopIteration` 异常。
## 初始化迭代器
```python
list1 = [1,2,3,'a','b','c']
list_iter = list1.__iter__() # list 是可迭代对象,这里我们调用 iter 方法初始化一个迭代器l ist_iter
item = list_iter.__next__() # 这里通过 next 方法来获取下一个数据
print(item)
item = list_iter.__next__()
print(item)
item = list_iter.__next__()
print(item)
item = list_iter.__next__()
print(item)
item = list_iter.__next__()
print(item)
item = list_iter.__next__()
print(item)
```
如果超出迭代范围,会触发 **StopIteration** 异常。我们可以加上异常处理,取完值后自动停止
```python
list1 = [1,2,3,'a','b','c']
list_iter = list1.__iter__()
while True:
try:
print(next(list_iter)) # 这里是 next 方法的另一种写法
except StopIteration:
print('迭代完成')
break
```
## 如何判断一个对象是迭代器
我们同样可以用内置的 `isinstance()` 方法来判断某个对象是否是**Iterator 对象(迭代器)**
```python
from collections.abc import Iterator
list1 = [1,2,3,'a','b','c']
list_iter = list1.__iter__()
print(isinstance(list1, Iterator))
print(isinstance(list_iter, Iterator))
print(isinstance(iter(list1), Iterator)) # 初始化迭代器的另一种方法
```
## for 循环的本质
我们常用的 for 循环其实本质上就是迭代器协议的一种具体实现,为我们提供了一个遍历的迭代元素的方法。
**工作原理:**
当你使用 `for` 循环遍历一个可迭代对象时,实际上发生了以下几个步骤:
1. 调用 `__iter__()`
- `for` 循环首先调用对象的 `__iter__()` 方法,获取一个迭代器对象。
2. 调用 `__next__()`
- 然后,`for` 循环在迭代器上反复调用 `__next__()` 方法,以获取下一个元素。
3. 处理 `StopIteration`
- 一旦 `__next__()` 抛出 `StopIteration` 异常,`for` 循环停止迭代。
# 生成器
生成器是 Python 中一种特殊的迭代器,允许你以一种简单而高效的方式生成序列。
## 生成器的原理
状态保持:生成器通过 `yield` 语句保存函数的状态。在每次调用生成器时,函数会从上一个 `yield` 语句的下一行继续执行,而不仅仅是从函数的开始处执行。
迭代器接口:生成器实现了迭代器协议,因此可以使用 `for` 循环进行遍历。
## 生成器函数
生成器也是一个函数,但与普通函数不同的是,它使用 `yield` 关键字来返回值。每次调用生成器函数时,它会从上次 `yield` 的地方继续执行,直到遇到下一个 `yield` 或函数结束。
调用生成器函数不会得到返回的具体的值,而是得到一个迭代器。每一次获取这个迭代器值,就能推动函数的执行,获取新的返回值。直到函数执行结束。
```python
def numbers(n):
"""生成从 1 到 n 的自然数"""
for i in range(1,n+1):
yield i
for i in numbers(10):
print(i)
```
## 惰性求值
生成器在需要时生成值,而不是一次性计算和返回所有值。这可以节省内存,特别是处理大型数据集时。
### 案例:重生之我在早餐的卖包子
我重生了,重生在了高考的前一天,由于上一世我参加了高考最后只能上个大专,毕业了一事无成。这一生,我要成为商业巨头……
一抬头,有一个卖包子的店铺正在转让,我决定从这里开始我的梦幻人生……
言归正传如果卖包子那么我一下子生成100笼包子没地方放的同时还容易坏。我们可不可以等到有顾客下单的时候再去生成
```python
def produce():
# 生产包子
for i in range(1,100):
yield f'生产了第{i}笼包子'
produce_g = produce()
print(produce_g.__next__())
print(produce_g.__next__())
print(produce_g.__next__())
# 顾客下单了需要5笼包子
for i in range(5):
print(produce_g.__next__())
# Output:
生产了第1笼包子
生产了第2笼包子
生产了第3笼包子
生产了第4笼包子
生产了第5笼包子
生产了第6笼包子
生产了第7笼包子
生产了第8笼包子
```
## send
- send 获取下一个值的效果和next基本一致
- 在获取下一个值的时候,给上一个 yield 的位置传递一个数据
- 使用 send 的注意事项
- 第一次使用生成器的时候 是用 nex t获取下一个值
- 最后一个 yield 不能接受外部的值
```python
def generator():
print(123)
content = yield 1
print('欢迎来到',content)
print(456)
yield 2
g = generator()
ret = g.__next__()
print('***',ret)
ret = g.send('英格科技')
print('***',ret)
```