天生骄傲
生成器
直接总结
- 创建生成器的方法
- 生成器表达式:
(i for i in [1, 2])
- yield: 函数中出现yield这个函数就是生成器,函数(生成器)执行到yield时会返回yield后面的值,并暂停,知道下次被唤醒后会从暂停处接着执行
- 生成器表达式:
- 迭代生成器: 生成器实现了python迭代协议,可以使用
next()
或for
迭代 - 向生成器发送消息:如果yield出现在表达式右边,下一次生成器被send()唤醒时,上一个yield会接收send()发过来的消息赋值给上一个yield左边的变量。
- 唤醒生成器:
- next():唤醒生成器,让他运行到下一个yield处,返回yield出来的值
- send():唤醒生成器,并给生成器发送一个消息,同时让他运行到下一个yield处,返回yield出来的值,不能直接一开始就发送具体的数据
- 停止生成器:close()
- 发送异常:throw(type, val)
- yield from
创建与使用生成器
直接使用生成器表达式就可以快速创建一个生成器
代码语言:javascript复制gen = (i for i in range(10))
print(type(gen))
for i in gen:
print(i, end=" ")
# <class 'generator'>
# 0 1 2 3 4 5 6 7 8 9
生成器实现了__next__
可以使用next()
来获取下一个值,当然也可以使用for
循环遍历
更加常见的创建生成器的方式是使用yield
关键字,一个函数如果出现yield
关键字这个函数就会变成生成器,当函数运行到yield
时会暂停下来,”返回“一个结果,下一次唤醒生成器时,函数会从停下来的地方继续运行
def builder_demo():
yield 0
yield 1
return 3
if __name__ == '__main__':
bd = builder_demo()
print(type(bd))
print(next(bd))
print(next(bd))
print(next(bd))
print(next(bd))
代码语言:javascript复制<class 'generator'>
0
1
Traceback (most recent call last):
File "E:/python/coroutine_test.py", line 12, in <module>
print(next(bd))
StopIteration: 3
当没有下一个元素时调用next会抛出StopIteration
异常,return的值会作为异常的值
if __name__ == '__main__':
bd = builder_demo()
while True:
try:
print(next(bd))
except StopIteration as e:
print(f'result is {e.value}')
break
代码语言:javascript复制0
1
result is 3
yield不但可以“传递出值”,也可以接收值
代码语言:javascript复制def builder_demo():
news = yield 0
print(f'news: {news}')
news1 = yield 1
print(f'new1: {news1}')
return 3
if __name__ == '__main__':
bd = builder_demo()
print(next(bd))
result1 = bd.send("hello")
print(result1)
result2 = bd.send("hello2")
print(result2)
代码语言:javascript复制0
news: hello
1
new1: hello2
Traceback (most recent call last):
File "E:python/coroutine_test.py", line 14, in <module>
result2 = bd.send("hello2")
StopIteration: 3
往暂停处传递消息使用生成器的send()
方法,这个方法还可以自动迭代到生成器中的下一个对象(有next())的作用。
生成器是先yield出数据,等到下一次生成器被唤醒时,才会接收send()
的数据,然后再yield出下一个数据,所以不能一开始就直接调用send()
发送具体的值,会抛出TypeError
TypeError: can't send non-None value to a just-started generator
应该先执行一次next()
或执行一次generator.send(None)
,让生成器yield出数据,send(None)
的作用与next()
基本一样
生成器也可以停止,使用close()
方法
def builder_demo():
news = yield 0
print(f'news: {news}')
news1 = yield 1
print(f'new1: {news1}')
yield 4
yield 5
return 3
if __name__ == '__main__':
bd = builder_demo()
# print(next(bd))
print(bd.send(None))
result1 = bd.send("hello")
print(result1)
result2 = bd.send("hello2")
print(result2)
bd.close()
print(next(bd))
代码语言:javascript复制0
news: hello
1
new1: hello2
4
Traceback (most recent call last):
File "E:python/coroutine_test.py", line 20, in <module>
print(next(bd))
StopIteration
在close()
之后再使用next()
,会抛出StopIteration异常
除此之外,还可以向生成器发送异常
代码语言:javascript复制if __name__ == '__main__':
bd = builder_demo()
print(bd.send(None))
bd.throw(Exception, TypeError("throw new error"))
print(next(bd))
代码语言:javascript复制0
Traceback (most recent call last):
File "E:python/coroutine_test.py", line 19, in <module>
bd.throw(Exception, TypeError("throw new error"))
File "E:python/coroutine_test.py", line 2, in builder_demo
news = yield 0
TypeError: throw new error
yield from
yield from 是python3.3 PEP380 新添加的特性,它允许将一个生成器的部分操作委派给另一个生成器,除了向子生成器委派任务,yield from也可以直接作用于迭代器,将迭代器中的每个对象逐一yield出来,如:
代码语言:javascript复制def demo(*args, **kwargs):
for i in args:
for j in i:
yield j
# 等价于
def demo(*args, **kwargs):
for i in args:
yield from i
上面的函数其实就是itertools.chain() 作用是将多个迭代器中的元素迭代出来
生成器嵌套
1、
调用方
:调用委派生成器的客户端(调用方)代码 2、委托生成器
:包含yield from表达式的生成器函数 3、子生成器
:yield from后面加的生成器函数
yield from 可以架设一个调用方
到子生成器
之间的双向桥梁
final_result = {}
def calculate():
total = 0
nums = []
while True:
info = yield
if not info:
break
total = info
nums.append(info)
# return 的值会被赋值给yield from 左边的变量
return total, nums
def middle(key: str, gen):
while True:
final_result[key] = yield from gen()
print(final_result)
def main():
data = {
"apple": [230, 569, 234, 776],
"banana": [564, 213, 798, 327],
"strawberry": [98, 76, 120, 436, 55],
"orange": [78, 67, 345, 124]
}
for key, value in data.items():
# 不要传递calculate()!
mid = middle(key, calculate)
mid.send(None) # 初激
for v in value:
mid.send(v)
mid.send(None)
if __name__ == '__main__':
main()
为什么“多此一举”架设一个“桥梁”?
- yield from 在内部处理了大量可能的异常,简化开发,提高代码安全性和可读性
生成器的作用
- 适用于对大量数据的处理:如果要对产生的大量数据进一步处理时,使用容器就只能先得到所有要处理的数据,存到容器,再逐个对容器中的数据遍历,再加工,最后得到目标数据,这样第一步产生的“中间数据”只用一次,但仍需大量空间储存;使用生成器可以产生一个,加工一个,节约内存,提高效率
- 用于协程