Python协程
28 August 2015
###使用生成器作为协程支持,可以实现简单的事件调度模型:
from time import sleep
# Event Manager
event_listeners = {}
def fire_event(name):
event_listeners[name]()
def use_event(func):
def call(*args, **kwargs):
generator = func(*args, **kwargs)
# 执行到挂起
event_name = next(generator)
# 将“唤醒挂起的协程”注册到事件管理器中
def resume():
try:
next(generator)
except StopIteration:
pass
event_listeners[event_name] = resume
return call
# Test
@use_event
def test_work():
print("=" * 50)
print("waiting click")
yield "click" # 挂起当前协程, 等待事件
print("clicked !!")
if __name__ == "__main__":
test_work()
sleep(3) # 做了很多其他事情
fire_event("click") # 触发了 click 事件
测试运行可以看到,打印出waiting click
之后,暂停了三秒,也就是协程被挂起,控制权回到主控制流上,之后触发“click”事件,协程被唤醒。
协程的这种“挂起”和“唤醒”机制实质上是将一个过程切分成了若干个子过程,给了我们一种以扁平的方式来使用事件回调模型。
###用greenlet 实现简单事件框架
用生成器实现的协程有些繁琐,同时生成器本身也不是完整的协程实现,因此经常有人批评 Python 的协程比 Lua 弱。其实 Python 中只要放下生成器,使用第三方库 greenlet,就可以媲美 Lua 的原生协程了。greenlet 提供了在协程中直接切换控制权的方式,比生成器更加灵活、简洁。
基于把协程看成“切开了的回调”的视角,我使用 greenlet 制作了一个简单的事件框架。
from greenlet import greenlet, getcurrent
class Event(object):
def __init__(self, name):
self.name = name
self.listeners = set()
def listen(self, listener):
self.listeners.add(listener)
def fire(self):
for listener in self.listeners:
listener()
class EventManager(object):
def __init__(self):
self.events = {}
def register(self, name):
self.events[name] = Event(name)
def fire(self, name):
self.events[name].fire()
def await(self, event_name):
self.events[event_name].listen(getcurrent().switch)
getcurrent().parent.switch()
def use(self, func):
return greenlet(func).switch
使用这个事件框架,可以很容易的完成挂起过程 -> 转移控制权 -> 事件触发 -> 唤醒过程的步骤。还是上文生成器协程中使用的例子,用基于 greenlet 的事件框架实现出来是这样的:
from time import sleep
from event import EventManager
event = EventManager()
event.register("click")
@event.use
def test(name):
print "=" * 50
print "%s waiting click" % name
event.await("click")
print "clicked !!"
if __name__ == "__main__":
test("micro-thread")
print "do many other works..."
sleep(3) # do many other works
print "done... now trigger click event."
event.fire("click")