文章目录
- @[toc]
- 什么是生成器
- 生成器示例
- 生成器工作流程
- 生成器表达式
- `send()`方法和`close()`方法
- `send()`方法
- `close()`方法
文章目录
- @[toc]
- 什么是生成器
- 生成器示例
- 生成器工作流程
- 生成器表达式
- `send()`方法和`close()`方法
- `send()`方法
- `close()`方法
个人主页:丷从心.
系列专栏:Python基础
学习指南:Python学习指南
什么是生成器
- 在
Python
中,使用生成器可以很方便地支持迭代器协议 - 生成器通过生成器函数产生,通过
def
定义,但不是通过return
返回,而是通过yield
一次返回一个结果,在每个结果之间挂起和继续它们的状态,来实现迭代器协议 yield
本质上是一个语法糖,内部是一个状态机,维护着挂起和继续的状态,从而支持迭代器协议
生成器示例
def my_range(n):
i = 0
while i < n:
yield i
i += 1
my_range = my_range(3)
print(my_range)
print(next(my_range))
print(next(my_range))
print(next(my_range))
<generator object my_range at 0x0000019CE75804A0>
0
1
2
- 在这个例子中,定义了一个生成器函数
my_range()
- 调用生成器函数会返回一个生成器对象,本质上是返回生成器对象的迭代器,通常直接称为生成器
- 既然生成器本质上是一个迭代器,那么其内部需要实现
__iter__()
方法和__next__()
方法,dir()
函数可以获取一个对象的所有属性和方法,可以使用dir()
函数查看生成器对象的属性和方法
def my_range(n):
i = 0
while i < n:
yield i
i += 1
my_range = my_range(3)
print(dir(my_range))
print('__iter__' in dir(my_range))
print('__next__' in dir(my_range))
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
True
True
生成器工作流程
- 在上面的例子中加入一些打印信息,进一步看看生成器的工作流程
def my_range(n):
print('开始迭代...')
print('-' * 10)
i = 0
while i < n:
print('迭代中...')
yield i
print('-' * 10)
i += 1
print('迭代结束')
gen_obj = my_range(3)
print(next(gen_obj))
print(next(gen_obj))
print(next(gen_obj))
开始迭代...
----------
迭代中...
0
----------
迭代结束
迭代中...
1
----------
迭代结束
迭代中...
2
- 通过结果可以看到:
- 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有运行
- 当第一次调用
next()
方法的时候,生成器函数才开始运行,运行到yield
语句处停止,并将i
作为返回值返回,执行过程为图中的①② - 当继续调用
next()
方法的时候,生成器函数从上一次停止的地方,也就是yield
语句处继续运行,直到再次运行到yield
语句处停止, 并将i
作为返回值返回,执行过程为图中的③④② - 如果下一次迭代不能运行到
yield
语句,就抛出StopIteration
异常,生成器在当前yield
语句处停止,不再执行③④②过程
生成器表达式
- 在介绍生成器表达式之前,先看看我们已经比较熟悉的列表解析
nums = [i for i in range(10) if i % 2]
print(nums)
[1, 3, 5, 7, 9]
- 上述列表解析返回一个包含 1 1 1到 10 10 10之间所有奇数的列表
- 当序列很长,而每次只需要获取一个元素时,应当考虑生成器而不是列表解析
- 生成器表达式的语法和列表解析一样,只不过生成器表达式是被
()
括起来的
gen_obj = (i for i in range(10) if i % 2)
print(gen_obj)
<generator object <genexpr> at 0x000001D6DC1E04A0>
- 生成器表达式并不是返回一个列表,而是返回一个生成器
- 使用生成器时,每迭代一次会产生(
yield
)出一个值来,实现了惰性加载(懒加载),只有在被迭代时才被赋值,并覆盖上一个值,所以在序列较长的情况下,使用生成器会优化内存
send()
方法和close()
方法
- 生成器中有两个重要的方法:
send()
方法和close
方法
send()
方法
- 在
Python 2.5
中,yield
语句变成了yield
表达式,也就是说yield
可以有一个值,而这个值就是send()
方法的参数,通过调用send()
方法将值传递给yield
,而send()
的返回值为yield
返回的值,所以send(None)
和next()
是等效的 - 注意,调用
send()
传入非None
值前,生成器必须处于挂起状态,否则将抛出异常,也就是说,第一次运行生成器时,只能使用next()
或send(None)
,因为没有yield
来接收这个值
def my_range(n):
i = 0
while i < n:
var = yield i
print(f'var 的值为 {var}')
i += 1
my_range = my_range(3)
print(my_range.send(None))
print(my_range.send('hello'))
print(my_range.send('world'))
0
var 的值为 hello
1
var 的值为 world
2
close()
方法
close()
方法用于关闭生成器,对关闭后的生成器再次调用next()
或send()
将抛出StopIteration
异常
def my_range(n):
i = 0
while i < n:
var = yield i
print(f'var 的值为 {var}')
i += 1
my_range = my_range(3)
print(my_range.send(None))
print(my_range.send('hello'))
my_range.close()
print(my_range.send('world'))
0
var 的值为 hello
1
Traceback (most recent call last):
File "C:/Users/FOLLOW_MY_HEART/Desktop/Python基础/【Python基础】生成器/test.py", line 18, in <module>
print(my_range.send('world'))
StopIteration