前言
使用 Python 的人都知道,Python 世界有 gevent 这么个协程库,既优雅(指:接口比较不错),性能又不错,在对付 IO bound 的程序时,不失为一个比较好的解决方案。
在使用 gevent 时,有一步是 patch 标准库,即:gevent 对标准库中一些同步阻塞调用的接口,自己进行了重新实现,并且让应用层对标准库的相关接口调用,全部重定向 gevent 的实现,以达到全异步的效果。 这一步比较有意思,让人不禁对其实现感到好奇,因为这种 patch 完全是在后台默默进行的,应用层根本不知道。如果我们想实现看某个接口不惯,自己想替换它,但是又不想应用层代码感知到 的效果,完全可以借鉴 gevent 的做法。
先是 Google 了一番,没有搜到满意的结果,看来还得自己亲自看代码。这篇文章即是记录了对应的探索历程。
我们的简单猜想推测
gevent 有个接口的签名如下:
def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True, httplib=False,
subprocess=True, sys=False, aggressive=True, Event=False,
builtins=True, signal=True):
可见 gevent 做了相当多的事情。但是标准库代码很庞大,gevent必然只会替换其中部分接口,其余的接口仍然是使用标准库。所以当应用层import socket
时,有些接口使用的是标准库的实现,有些则是使用 gevent 的实现。
按照这种推测,理论上可以对所有看不惯的库动手脚,不管是标准库,还是第三方库。
源码剖析
我们由入口进,首先便看到如下代码(为了便于观看,去掉了注释和一些边缘逻辑代码):
def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True, httplib=False,
subprocess=True, sys=False, aggressive=True, Event=False,
builtins=True, signal=True):
_warnings, first_time = _check_repatching(**locals())
if not _warnings and not first_time:
return
if os:
patch_os()
if time:
patch_time()
if thread:
patch_thread(Event=Event)
if sys:
patch_sys()
if socket:
patch_socket(dns=dns, aggressive=aggressive)
if select:
patch_select(aggressive=aggressive)
if ssl:
patch_ssl()
if httplib:
raise ValueError('gevent.httplib is no longer provided, httplib must be False')
if subprocess:
patch_subprocess()
if builtins:
patch_builtins()
if signal:
if not os:
_queue_warning('Patching signal but not os will result in SIGCHLD handlers'
' installed after this not being called and os.waitpid may not'
' function correctly if gevent.subprocess is used. This may raise an'
' error in the future.',
_warnings)
patch_signal()
_process_warnings(_warnings)
patch_os 的逻辑如下:
def patch_os():
patch_module('os')
patch_module 的逻辑如下:
def patch_module(name, items=None):
gevent_module = getattr(__import__('gevent.' + name), name)
module_name = getattr(gevent_module, '__target__', name)
module = __import__(module_name)
if items is None:
items = getattr(gevent_module, '__implements__', None)
if items is None:
raise AttributeError('%r does not have __implements__' % gevent_module)
for attr in items:
patch_item(module, attr, getattr(gevent_module, attr))
return module
真正干活的 patch_item :
def patch_item(module, attr, newitem):
NONE = object()
olditem = getattr(module, attr, NONE)
if olditem is not NONE:
saved.setdefault(module.__name__, {}).setdefault(attr, olditem)
setattr(module, attr, newitem)
总结
根据上面的描述,核心代码就一行,简单且优雅:
setattr(target_module, interface_name, gevent_impl)
这也让我们再次领略到了动态语言为框架/库设计者带来的便利,即:可以比较容易地去hack 整个语言。具体到 gevent,我们只需要有如下知识储备,便可比较容易地了解整个 patch 过程:
__import__ 给定一段字符串,会根据这个字符串,将对已经 module 加载进来
一切皆对象 在Python中,module是对象,int是对象,一切都是对象,而且可以动态地添加属性
setattr/getattr/hasattr 三大工具函数,动态去操纵每一个 object