第九章 Python之生成器
生成器概念
- 生成器函数:函数体中如果出现了
yield关键字,那么该函数就叫做生成器函数。 - 生成器对象:调用
生成器函数时,其函数体不会立即执行,而是返回一个生成器对象。
备注:不管能否执行到yield所在的位置,只要有yield,那该函数就是生成器函数。
def test():
print('test函数开始执行...')
print(100)
yield
a = 200
print(a)
t = test()
print(t)
## 执行结果:
<generator object test at 0x000001F3262B1F50>
## 代码解释:体会上面的概念,此处为什么执行结果返回的是一个生成器对象,而没有打印函数内部的print语句结果。
生成器细节
写在生成器函数中的代码,需要通过生成器对象来执行:
- 调用
生成器对象的__next__方法,会让生成器函数中的代码开始执行。 - 当
生成器函数中的代码开始执行后,遇到yield会暂停,并会记录暂停的位置。 - 后续调用
__next__方法时,都会从上一次暂停的位置,继续运行,直到再次遇见yield。 - yield后面所写的表达式,会作为本次
__next__方法的返回值。
def test():
print('test函数开始执行了...')
print(100)
yield '我是第一个yield所返回的数据'
a = 200
print(a)
yield '我是第二个yield所返回的数据'
b = 300
print(b)
return 'hello yield'
t = test()
t1 = next(t)
print(t1)
t2 = next(t)
print(t2)
try:
next(t)
except StopIteration as e:
print(e)
## 执行结果:
test函数开始执行了...
100
我是第一个yield所返回的数据
200
我是第二个yield所返回的数据
300
hello yield
## 代码解释:最后一个next(t)如果不适用异常机制捕获,会执行破异常,那么return的值不会返回,此处得到hello yield其实是异常捕获的结果。
生成器对象其实就是一种特殊的迭代器(本质是通过yield自动实现了迭代器协议,上一篇讲过迭代器,忘记的同学可以看上一篇)
def test():
print('test函数开始执行了...')
print(100)
yield '我是第一个yield所返回的数据'
a = 200
print(a)
yield '我是第二个yield所返回的数据'
b = 300
print(b)
return 'hello yield'
t = test()
# 验证:生成器对象t,和迭代器一样,也拥有:__iter__,__next__方法。
print(hasattr(t, '__iter__'))
print(hasattr(t, '__next__'))
## 执行结果如下:
True
True
def test():
print('test函数开始执行了...')
print(100)
yield '我是第一个yield所返回的数据'
a = 200
print(a)
yield '我是第二个yield所返回的数据'
b = 300
print(b)
return 'hello yield'
t = test()
# 验证:生成器对象的__iter__方法和迭代器是一样的,返回的也是自身。
result = iter(t)
print(result == t)
## 执行结果如下:
True
def test():
print('test函数开始执行了...')
print(100)
yield '我是第一个yield所返回的数据'
a = 200
print(a)
yield '我是第二个yield所返回的数据'
b = 300
print(b)
return 'hello yield'
t = test()
# 模拟分析:for循环背后的逻辑
gen = iter(t)
while True:
try:
value = next(gen)
print(value)
except StopIteration:
break
## 执行结果如下:
test函数开始执行了...
100
我是第一个yield所返回的数据
200
我是第二个yield所返回的数据
300
yield也可以写在循环里面
def create_compary(total):
for index in range(1, total + 1):
yield f'我是第{index}家公司'
# companys是生成器对象
companys = create_compary(5)
# 调用一次companys的__next__方法,就会得到一家公司
c1 = next(companys)
print(c1)
c2 = next(companys)
print(c2)
c3 = next(companys)
print(c3)
c4 = next(companys)
print(c4)
c5 = next(companys)
print(c5)
## 执行结果如下:
我是第1家公司
我是第2家公司
我是第3家公司
我是第4家公司
我是第5家公司
for company in companys:
print(company)
## 执行结果如下:
我是第1家公司
我是第2家公司
我是第3家公司
我是第4家公司
我是第5家公司
## 解释:直接执行for循环是没有结果的,迭代器中讲过,迭代器是会被消耗的,上面已经调用了5次next方法,如果要让for循环打印公司,需要注释掉上面的5次next执行。
yield from能把一个可迭代对象里的东西依次yield出来,可以替代for + yield。
def test():
nums = [100,200,300,400]
yield from nums
t = test()
t1 = next(t)
print(t1)
t2 = next(t)
print(t2)
t3 = next(t)
print(t3)
t4 = next(t)
print(t4)
## 执行结果如下:
100
200
300
400
# 和下面的for循环式一个样的
for item in t:
print(item)
使用生成器.send(值)可以让生成器继续执行的同时,给上一次yield传值。
- 第一次启动生成器时,不能传值。(或者说只能传递None值)。
def test():
print('test函数开始执行了...')
print(100)
a = yield '我是第一个yield所返回的数据'
print(a)
b = yield '我是第二个yield所返回的数据'
print(b)
return 'hello yield'
t = test()
t1 = t.send(None)
print(t1)
t2 = t.send('这是传给a的值')
print(t2)
try:
t.send('这是传给b的值')
except StopIteration as e:
print(e)
# 执行结果如下:
test函数开始执行了...
100
我是第一个yield所返回的数据
这是传给a的值
我是第二个yield所返回的数据
这是传给b的值
hello yield
生成器的应用
用生成器实现遍历Perosn类的实例对象:
# 用生成器实现遍历Person类的实例对象
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
self.__attr = [name, age, gender, address]
def __iter__(self):
# yield self.name
# yield self.age
# yield self.gender
# yield self.address
# 上面注释调的写法等价于下面这段代码
yield from self.__attr
p1 = Person('孙悟空', 99 , '猴', '花果山水帘洞')
for attr in p1:
print(attr)
# 执行结果如下:
孙悟空
99
猴
花果山水帘洞
用生成器实现斐波那契数列:
# 用生成器实现斐波那契
def fibo(total):
pre = 1
cur = 1
for index in range(total):
if index < 2:
yield 1
else:
value = pre + cur
pre = cur
cur = value
yield value
f1 = fibo(10)
for item in f1:
print(item, end=' ')
# 执行结果如下:1 1 2 3 5 8 13 21 34 55
无论是迭代器,还是生成器对象,都可以使用list,tuple,set等直接拿到其里面的所有内容。
注意:如果数据量很大,可能挤爆内存,造成内存溢出。
def fibo(total):
pre = 1
cur = 1
for index in range(total):
if index < 2:
yield 1
else:
value = pre + cur
pre = cur
cur = value
yield value
f1 = fibo(10)
result = set(f1)
print(result)
# 执行结果如下:
{1, 2, 3, 34, 5, 8, 13, 21, 55}
生成器表达式
生成器表达式:一种用于类似列表推导式的语法,快速创建生成器对象的方式。
语法格式:表达式 for 变量 in 可迭代对象
什么时候适合用生成器表达式?
答:当每个结果,只依赖当前这一个元素时。
nums = [100, 200, 300, 400]
# 列表推导式
res1 = [n * 2 for n in nums]
print(res1) # [200, 400, 600, 800]
# 生成器表达式
res2 = [n * 2 for n in nums]
for item in res2:
print(item, end=" ") # 200 400 600 800
# 代码解释:生成器表达式,细细体会当‘每个结果,只依赖当前这一个元素时’这句话。此处的n就可以理解作用于nums中当前的每个元素,且只依赖于nums当前的这一个元素。