协程 生成器 and coroutine(一)
摘要:记录研究python协程过程中生成器和coroutine的使用
生成器
可迭代对象
判断:Iterable类与可迭代对象对应,可以使用isinstance判断一个对象是否是可迭代对象。
>>> from collections.abc import Iterable
>>> print(isinstance(123, Iterable))
False
>>> print(isinstance('abc', Iterable))
True
>>> print(isinstance([], Iterable))
True
>>> print(isinstance({}, Iterable))
True
>>> print(isinstance((), Iterable))
True
定义:只要一个对象定义了__iter__()方法,那么它就是可迭代对象。
from collections.abc import Iterable
class A():
def __iter__(self):
pass
print('A()是可迭代对象吗:',isinstance(A(),Iterable))
迭代器
判断:Iterator与迭代器对应,可以使用isinstance判断一个对象是否是迭代器。
from collections.abc import Iterable
from collections.abc import Iterator
class B():
def __iter__(self):
pass
def __next__(self):
pass
print('B()是可迭代对象吗:', isinstance(B(), Iterable))
print('B()是迭代器吗:', isinstance(B(), Iterator))
输出:
B()是可迭代对象吗: True
B()是迭代器吗: True
定义:如果一个对象同时实现了__iter__()和__next()__()方法,那么它就是迭代器。
可见,迭代器一定是可迭代对象,但可迭代对象不一定是迭代器。
关于 可迭代对象 和 迭代器 的 详细说明参考 为什么for循环可以遍历list:Python中迭代器与生成器 - 奥辰 - 博客园
生成器
定义:如果一个函数体内部使用yield关键字,这个函数就称为生成器函数,生成器函数调用时产生的对象就是生成器。生成器是一个特殊的迭代器,在调用该生成器函数时,Python会自动在其内部添加__iter__()方法和__next__()方法。把生成器传给 next() 函数时, 生成器函数会向前继续执行, 执行到函数定义体中的下一个 yield 语句时, 返回产出的值, 并在函数定义体的当前位置暂停, 下一次通过next()方法执行生成器时,又从上一次暂停位置继续向下……,最终, 函数内的所有yield都执行完,如果继续通过yield调用生成器, 则会抛出StopIteration异常——这一点与迭代器协议一致。
>>> import inspect
>>> from collections.abc import Iterable
>>> from collections.abc import Iterator
>>> def gen():
... print('第1次执行')
... yield 1
... print('第2次执行')
... yield 2
... print('第3次执行')
... yield 3
...
>>> inspect.isgeneratorfunction(gen) # 判断g是否是生成器函数
True
>>> g = gen()
>>> isinstance(g, Iterable) # 判断g是否是可迭代对象
True
>>> isinstance(g, Iterator) # 判断g是否是迭代器
True
>>>inspect.isgenerator(g) # 判断g是否是生成器对象
True
>>> print(g)
<generator object gen at 0x000001EC6F98A348>
>>> next(g)
第1次执行
1
>>> next(g)
第2次执行
2
>>> next(g)
第3次执行
3
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
注意这个StopIteration异常,可以用来获取生成的器的返回值。如果没有return,则默认生成器执行完毕时返回StopIteration
def gen():
print('第1次执行')
yield 1
print('第2次执行')
yield 2
print('第3次执行')
yield 3
return "111"
a = gen()
try:
next(a)
next(a)
next(a)
next(a)
except StopIteration as exc:
print(exc.value)
'''运行结果为:
第1次执行
第2次执行
第3次执行
111
'''
生成器的使用
使用close关闭生成器
def my_generator(): yield 1 yield 2 yield 3 yield 4 g = my_generator() print(next(g)) print(next(g)) g.close() print(next(g)) # 在此处会显示错误 # print(next(g)) '''运行结果为: 1 2 显示StopIteration '''
send和next方法
send方法:def my_generator(): value1 = yield "123" print("我是send函数发送的值1", value1) value2 = yield "789" print("我是send函数发送的值2", value2) yield "101112" g = my_generator() print("-"*20) print(g.send(None)) # 启动生成器必须用send(None) print("-"*20) print(g.send("值1")) print("-"*20) print(g.send("值2")) print("-"*20) print(g.send(None)) # 生成器结束,抛出StopIteration的异常 '''运行结果为: -------------------- 123 -------------------- 我是send函数发送的值1 值1 789 -------------------- 我是send函数发送的值2 值2 101112 -------------------- '''
解释:启动生成器g必须要用send(None),不能使用send("值1")这种形式。
第一步:
g.send(None)激活生成器之后,程序运行到 value1 = yield "123",抛出"123"后,生成器暂停运行,在控制台打印出"123"。
第二步:
g.send("值1")重新激活生成器并且给 value1 赋值为 "值1",然后运行到 value2 = yield "789" ,抛出"789"后,生成器暂停运行,在控制台打印出"789"。
第三步:
g.send("值2")重新激活生成器并且给 value2 赋值为 "值2",然后运行到yield "101112",抛出"101112"后,生成器暂停运行,在控制台打印出"101112"。
第四步:
g.send(None),重新激活生成器,但是因为后面没有yield了,生成器执行完毕,抛出StopIteration异常。next方法
next方法和send方法差不多,除了不能向生成器传递值。def my_generator(): value1 = yield "123" print("我是send函数发送的值1", value1) value2 = yield "789" print("我是send函数发送的值2", value2) yield "101112" g = my_generator() print("-"*20) print(next(g)) print("-"*20) print(next(g)) print("-"*20) print(next(g)) print("-"*20) print(next(g)) print("-" * 20) '''运行结果为: -------------------- 123 -------------------- 我是send函数发送的值1 None 789 -------------------- 我是send函数发送的值2 None 101112 -------------------- '''
throw方法
通过生成器throw方法抛出异常后,后面的所有yield就会被废除。def my_generator(): yield 'a' yield 'b' yield 'c' g = my_generator() print(next(g)) print(next(g)) print('-------------------------') try: print(g.throw(StopIteration)) except StopIteration as exc: print("异常值", exc.value) print(next(g))
可以看到 yield 'c' 会被废除。
对比下面的:def my_generator(): while True: try: yield 'a' yield 'b' yield 'c' yield 'd' yield 'e' except ValueError: print('触发“ValueError"了') except TypeError: print('触发“TypeError"了') g = my_generator() print(next(g)) print(next(g)) print('-------------------------') print(g.throw(ValueError)) print('-------------------------') print(next(g)) print(next(g)) print('-------------------------') print(g.throw(TypeError)) print('-------------------------') print(next(g))
解释如下:
出现这样的结果是不是很意外?它和上面的那个例子只有一个while只差,为什么结果差这么多,解释如下:首先print(next(g))两次:会输出a、b,并停留在c之前。
然后由于执行了g.throw(ValueError),所以会跳过所有后续的try语句,也就是说yield 'c'、yield 'd'、yield 'e'不会被执行,然后进入到except语句,打印出 触发“ValueError"了。然后再次进入到while语句部分,消耗一个yield,此时因为是重新进入的while,小号的依然是第一个yield 'a',所以会输出a。实际上这里的a也就是g.throw()的返回值,因为它返回的是下一个迭代的数;
然后在print(next(g))两次,会执行yield b’、yield 'c’语句,打印出b、c,并停留在执行完该语句后的位置,即yield 'd'之前。
然后再g.throw(TypeError):会跳出try语句,从而后面的d,e不会被执行,下次重新进入while,依然打印出a。
最后,执行了一次print(next(g)),打印出b。
更多参考:python协程系列(一)——生成器generator以及yield表达式详解_MIss-Y的博客-CSDN博客
yield from
yield from主要有下面三个作用:
第一个作用:针对yield无法获取生成器return的返回值。
如果要获取一个协程的return值,必须捕获StopIteration异常,然后从StopIteration异常中获取值
def my_generator():
for i in range(5):
if i==2:
return '我被迫中断了'
else:
yield i
def main(generator):
try:
print(next(generator)) #每次迭代一个值,则会显式出发StopIteration
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
except StopIteration as exc:
print(exc.value) #获取返回的值
g=my_generator()
main(g)
'''运行结果为:
0
1
我被迫中断了
'''
如果改用yield from 则可以使用
def my_generator():
for i in range(5):
if i==2:
return '我被迫中断了'
else:
yield i
def wrap_my_generator(generator): #定义一个包装“生成器”的生成器,它的本质还是生成器
result=yield from generator #自动触发StopIteration异常,并且将return的返回值赋值给yield from表达式的结果,即result
print(result)
def main(generator):
for j in generator:
print(j)
g=my_generator()
wrap_g=wrap_my_generator(g)
main(wrap_g) #调用
'''运行结果为:
0
1
我被迫中断了
'''
第二个作用:减少生成器的嵌套
(感觉是方便了一点,但是感觉这个作用应该也不是主要作用)
def gen_one():
subgen = range(10)
yield from subgen
def gen_two(): # gen_one和 gen_two效果一样
subgen = range(10)
for item in subgen:
yield item
第三个作用:在子生成器和原生成器的调用者之间打开双向通道,两者可以直接通信。
def gen():
yield from subgen()
def subgen():
while True:
x = yield
yield x+1
def main():
g = gen()
next(g) # 驱动生成器g开始执行到第一个 yield
retval = g.send(1) # 看似向生成器 gen() 发送数据
print(retval) # 返回2
g.throw(StopIteration) # 看似向gen()抛入异常
通过上述代码清晰地理解了yield from的双向通道功能。关键字yield from在gen()内部为subgen()和main()开辟了通信通道。main()里可以直接将数据1发送给subgen(),subgen()也可以将计算后的数据2返回到main()里,main()里也可以直接向subgen()抛入异常以终止subgen()。
对于这三个作用的详细说明参考
python协程系列(三)——yield from原理详解_MIss-Y的博客-CSDN博客
Python3 异步编程详解(史上最全篇)_木风卜雨的博客-CSDN博客_python3 异步编程
coroutine
旧式协程:Python3.4开始,新增了asyncio相关的API,语法使用@asyncio.coroutine和yield from实现协程
新式协程:Python3.5中引入async/await语法
协程的必要条件
新式协程中,只有同时具有async和await两个条件,才是协程函数。
下面的例子 test1不是协程对象,test2是协程对象。
例子:
import asyncio
async def test1(n):
i =0
while i < n:
yield i
i = i+1
async def test2(n):
await asyncio.sleep(5)
type1 = test1(1)
type2 = test2(1)
print(type(type1))
print(type(type2))
'''
运行结果:
<class 'async_generator'>
<class 'coroutine'>
'''
判断是否是协程
可以使用asyncio.iscoroutinefunction和asyncio.iscoroutine判断是否是协程函数和协程对象。
import asyncio
@asyncio.coroutine
def test_yield_from(n):
print(n)
# 是否是协程函数
print(asyncio.iscoroutinefunction(test_yield_from))
# 是否是协程对象
print(asyncio.iscoroutine(test_yield_from(3)))
@asyncio.coroutine与async源码分析
@asyncio.coroutine的源码
import functools
import types
import inspect
def coroutine(func):
"""Decorator to mark coroutines.
If the coroutine is not yielded from before it is destroyed,
an error message is logged.
"""
# 如果是async关键字修饰的新式协程,直接返回
if inspect.iscoroutinefunction(func):
# In Python 3.5 that's all we need to do for coroutines
# defined with "async def".
return func
# 检查是不是生成器函数,如果是直接返回
if inspect.isgeneratorfunction(func):
coro = func
# 如果不是生成器函数,就封装一个生成器函数
else:
@functools.wraps(func)
def coro(*args, **kw):
res = func(*args, **kw)
if (base_futures.isfuture(res) or inspect.isgenerator(res) or
isinstance(res, CoroWrapper)):
res = yield from res
else:
# If 'res' is an awaitable, run it.
try:
await_meth = res.__await__
except AttributeError:
pass
else:
# 判断生成器对象是不是await的
if isinstance(res, collections.abc.Awaitable):
res = yield from await_meth()
return res
wrapper = coro
# _is_coroutine是一个object对象,_is_coroutine = object()
# 使用@asyncio.coroutine创建的协程函数
# 不会被inspect.iscoroutinefunction认为是协程函数。
# asyncio.iscoroutinefunction判断函数是否是一个协程时
# 会通过_is_coroutine属性判断
wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction().
return wrapper
async的源码
我懒得去翻源码了直接略过....
async与@asyncio.coroutine的区别
import asyncio
import inspect
@asyncio.coroutine
def test1(n):
print(n)
async def test2(n):
print(n)
旧式写法 = test1(1)
新式写法 = test2(2)
print("使用@asyncio.coroutine关键字的协程")
print("使用inspect判断旧式写法是否是协程函数", inspect.iscoroutinefunction(test1))
print("使用inspect判断旧式写法是否是协程对象", inspect.iscoroutine(旧式写法))
print("使用asyncio判断旧式写法是否是协程函数", asyncio.iscoroutinefunction(test1))
print("使用asyncio判断旧式写法是否是协程对象", asyncio.iscoroutine(旧式写法))
print("-"*20)
print("使用async关键字的协程")
print("使用inspect判断新式写法是否是协程函数", inspect.iscoroutinefunction(test2))
print("使用inspect判断新式写法是否是协程对象", inspect.iscoroutine(新式写法))
'''
运行结果:
使用@asyncio.coroutine关键字的协程
使用inspect判断旧式写法是否是协程函数 False
使用inspect判断旧式写法是否是协程对象 False
使用asyncio判断旧式写法是否是协程函数 True
使用asyncio判断旧式写法是否是协程对象 True
--------------------
使用async关键字的协程
使用inspect判断新式写法是否是协程函数 True
使用inspect判断新式写法是否是协程对象 True
'''
inspect.iscoroutinefunction和asyncio.iscoroutinefunction的区别
使用@asyncio.coroutine创建的协程函数,不会被inspect.iscoroutinefunction认为是协程函数。asyncio.iscoroutinefunction判断函数是否是一个协程时,会通过_is_coroutine属性判断。
inspect.iscoroutinefunction源码
def iscoroutinefunction(object): """Return true if the object is a coroutine function. Coroutine functions are defined with "async def" syntax. """ return bool((isfunction(object) or ismethod(object)) and object.__code__.co_flags & CO_COROUTINE)
asyncio.iscoroutinefunction源码
def iscoroutinefunction(func): """Return True if func is a decorated coroutine function.""" return (inspect.iscoroutinefunction(func) or getattr(func, '_is_coroutine', None) is _is_coroutine)
协程的状态
>>> def simple_coroutine():
print("What's your name?")
name = yield
print("Hello", name, ", where are you from?")
nation = yield
print("OK, I see")
>>> from inspect import getgeneratorstate
>>> coro = simple_coroutine()
>>> getgeneratorstate(coro)
'GEN_CREATED'
>>> next(coro)
What's your name?
>>> getgeneratorstate(coro)
'GEN_SUSPENDED'
>>> coro.send('Li Yan')
Hello Li Yan , where are you from?
>>> getgeneratorstate(coro)
'GEN_SUSPENDED'
>>> coro.send('China')
OK, I see
Traceback (most recent call last):
File "<pyshell#92>", line 1, in <module>
coro.send('China')
StopIteration
>>> getgeneratorstate(coro)
'GEN_CLOSED'
除了'GEN_CREATED','GEN_SUSPENDED','GEN_CLOSED',还有'GEN_RUNNING',不过我们很少观察到此状态。
- GEN_CREATED:等待执行,即还没有进入协程
- GEN_RUNNING:解释器执行(这个只有在使用多线程时才能查看到他的状态,而协程是单线程的)
- GEN_SUSPENDED:在yield表达式处暂停(协程在暂停等待的时候的状态)
- GEN_CLOSED:执行结束(协程执行结束了之后的状态)
通过 coro = simple_coroutine() 我们得到了一个协程(生成器),此时它处于 'GEN_CREATED' 状态,我们还不能向它传递值,通过调用 next(coro) 启动协程,直到运行到 yield 暂停,等待我们传入数据,coro.send('Li Yan') 使 yield 赋值为'Li Yan',name 获得值 'Li Yan',并继续运行直到下一个yield,在我们传入数据 'China' 之后,nation获得值 'China' 并继续运行,直到触发了StopIteration,此时协程处于'GEN_CLOSED'状态。
调试代码备份
下面是研究协程和生成器时的代码备份。
参考
参考自:Python协程初探
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。