协程详解(二)
了解了协程的基础概念之后,接下来需要了解的是io多路复用了,具体的一些资料在我另外一篇文章专门了解一下
我这里也不讲它的具体的概念,只是讲一下io多路复用selecto具体的使用方法
看下面的伪代码:
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
sock = socket.socket()
def on_readable(): data = sock.recv(4096)
print(data) selector.register(sock.fileno(), EVENT_READ, on_readable)
while True: events = selector.select() for event_key, event_mask in events: callback = event_key.data callback()
我们分析一下这里的代码:
1. 定义了一个socket
2. 在select里面给socket注册了一个读事件的回调
3. 进行一个死循环,不断的执行
selector.select()
这个的代码的作用是什么呢,当注册在select里面的socket出现了事件中断的话,就马上返回对应的事件;如果没有,就一直阻塞。
在这里是只要是socket的读事件一旦就绪,就马上返回,这里的读事件表现为只要socket收到了数据,就代表我们可以对他进行数据读取了
4. 最后把socket对应的回调取出来,然后执行回调函数
所以这里的代码效果就是,不断地去读取socket的数据,然后打印出来
3.yield 和 yield from
我们这里套路的协程是基于python的生成器来展开,最原始的协程就是使用生成器来设计的,而且也是比较简单好懂的方案。
而其中yield和yield from充当了非常重要的一环,所以这里也需要进行讨论一下,一下是参考了其他文章
https://www.jianshu.com/p/9dd355ab4e5d
yield
简单的yield实例
yield
可以用来为一个函数返回值塞数据,比如下面的例子:
def addlist(alist):
for i in alist:
yield i + 1
取出alist
的每一项,然后把i + 1
塞进去。然后通过调用取出每一项:alist = [1, 2, 3, 4]
for x in addlist(alist):
print(x)
包含yield的函数
假如你看到某个函数包含了yield
,这意味着这个函数已经是一个Generator
,它的执行会和其他普通的函数有很多不同。比如下面的简单的函数:def h():
print('study yield')
yield 5
print('go on!')
h()
可以看到,调用h()
之后,print 语句并没有执行!这就是yield
。具体的内容后面会越来越清晰,包括yield
的工作原理。yield是一个表达式
python 2.5以前,yield
是一个语句,我也没有考证,因为早都不用了,现在yield
是一个表达式:m = yield 5
表达式(yield 5)的返回值将赋值给m,所以,m = 5
肯定是错的。
那么如何获取(yield 5)的返回值呢?需要用到send(msg)
。
yield工作原理
揭晓yield
的工作原理,需要配合next()
函数。上面的h()
被调用后并没有执行,因为它有yield
表达式,通过next()
可以恢复Generator
执行,直到下一个yield
。def h():
print('study yield')
yield 5
print('go on!')
c = h()
d1 = next(c) # study yield
d2 = next(c)
"""
study yield
go on!
Traceback (most recent call last):
File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
d2 = next(c)
StopIteration
"""
next()
被调用后,h()
开始执行,直到遇到yield 5
因此输出结果是:study yield
当我们再次调用next()
时,会继续执行,直到找到下一个yield
。由于后面没有yield
了,因此会拋出异常:
study yield
go on!
Traceback (most recent call last):
File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
d2 = next(c)
StopIteration
send(msg) 与 next()
了解了next()
如何让包含yield
的函数执行后,我们再来看另外一个非常重要的函数send(msg)
。
其实next()
和send()
在一定意义上作用是相似的
区别
send()
可以传递yield
的值
next()
只能传递None
。
所以next()
和 send(None)
作用是一样的。
def s():
print('study yield')
m = yield 5
print(m)
d = yield 16
print('go on!')
c = s()
s_d = next(c) # 相当于send(None)
c.send('Fighting!') # (yield 5)表达式被赋予了'Fighting!'
study yield
Fighting!
注意 生成器刚启动时(第一次调用),请使用next()
语句或是send(None)
,不能直接发送一个非None的值,否则会报TypeError
,因为没有yield
语句来接收这个值。send(msg) 与 next()的返回值
send(msg)
和 next()
的返回值比较特殊,是下一个yield
表达式的参数(yield 5,则返回 5)。
到这里,第一个例子中,通过for i in alist
遍历 Generator
,其实是每次都调用了next()
,而每次next()
的返回值正是yield
的参数:
def s():
print('study yield')
m = yield 5
print(m)
d = yield 16
print('go on!')
c = s()
s_d1 = next(c) # 相当于send(None)
s_d2 = c.send('Fighting!') # (yield 5)表达式被赋予了'Fighting!'
print('My Birth Day:', s_d1, '.', s_d2)
study yield
Fighting!
My Birth Day: 5 . 16
中断Generator
上面的例子中,当没有可执行程序的时候,会抛出一个StopIteration
, 开发过程中,中断Generator是一个非常灵活的技巧
throw
通过抛出一个GeneratorExit异常来终止Generator。
close
close的作用和throw是一样的,看它的源码,可以发现,它和raise一球样
def throw(self, type, value=None, traceback=None):
'''Used to raise an exception inside the generator.'''
# 用于在生成器中抛出一个异常。
pass
def close(self):
'''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''
# 在生成器中生成新的GeneratorExit异常来终止迭代。
pass
其实最后一个中断生成器可以忽略的,在开发过程中,不可避免的要用到这些,但是Python3内部已经做得很好了,一般不太需要手动去做这件事情。
yield from
上面介绍了yield是基本使用方法,但是上面yield有个问题,就是当生成器最后一次执行的时候,会抛出一个异常, 看个例子
def h():
print('study yield')
yield 5
print('go on!')
return 6
c = h()
d1 = next(c) # study yield
d2 = next(c)
"""
study yield
go on!
Traceback (most recent call last):
File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
d2 = next(c)
StopIteration
"""
假如我们想要获取生成器的值,就必须要进行捕获异常,然后从异常里面获取生成器的值,例子:
try:
d2 = next(c)
except StopIteration as e: # e.value 就是 return 返回的值 print("return value is", e.value) break
这样就非常不方便了,代码也不好看,有没有更好的方式呢,yield from就能够解决这个问题了,例子:
def a():
print('study yield')
yield 5
print('go on!')
return 6
def h():
result=yield from a()
print(result)
c = h()
d1 = next(c) # study yield
d2 = next(c) # go on 以及 6
使用了yield from 获取了生成器a(),a return回来的值可以直接赋值给result,这样就避免了使用异常捕捉这样挫的解决方式,
也提高了代码的可读性。