博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python gevent 是如何 patch 标准库的 ?
阅读量:6859 次
发布时间:2019-06-26

本文共 4116 字,大约阅读时间需要 13 分钟。

前言

使用 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):    # Check to see if they're changing the patched list    _warnings, first_time = _check_repatching(**locals())    if not _warnings and not first_time:        # Nothing to do, identical args to what we just        # did        return        # 显然,主逻辑在这里    # 无非是对每个模块实现对应的 patch 函数,因此,我们只需要看一个就够了    # order is important    if os:        patch_os()    if time:        patch_time()    if thread:        patch_thread(Event=Event)    # sys must be patched after thread. in other cases threading._shutdown will be    # initiated to _MainThread with real thread ident    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):    # name应该是模块名,items应该是需要替换的接口(命名为 interface_names 更合适 :) )        # 先 __import__ ,然后马上取到对应的 module object    gevent_module = getattr(__import__('gevent.' + name), name)    # 取到模块名    module_name = getattr(gevent_module, '__target__', name)    # 根据模块名,加载标准库, 比如,如果 module_name == 'os', 那么 os 标准库便被加载了    module = __import__(module_name)        # 如果外部没有指定需要替换的接口,那么我们自己去找    if items is None:        # 取到对应的接口        # 看 gevent 对应的模块 比如 gevent.os         # 果然有对应的变量        #  __implements__ = ['fork']        #  __extensions__ = ['tp_read', 'tp_write']        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):    # module: 目标模块    # attr:需要替换的接口    # newitem: gevent 的实现            NONE = object()    olditem = getattr(module, attr, NONE)    if olditem is not NONE: # 旧实现        saved.setdefault(module.__name__, {}).setdefault(attr, olditem)            # 替换为 gevent 的实现,原来这么简单!简单到不能再简单!    setattr(module, attr, newitem)复制代码

总结

根据上面的描述,核心代码就一行,简单且优雅:

setattr(target_module, interface_name, gevent_impl)复制代码

这也让我们再次领略到了动态语言为框架/库设计者带来的便利,即:可以比较容易地去hack 整个语言。具体到 gevent,我们只需要有如下知识储备,便可比较容易地了解整个 patch 过程:

__import__  给定一段字符串,会根据这个字符串,将对已经 module 加载进来一切皆对象  在Python中,module是对象,int是对象,一切都是对象,而且可以动态地添加属性setattr/getattr/hasattr  三大工具函数,动态去操纵每一个 object复制代码

转载地址:http://snxyl.baihongyu.com/

你可能感兴趣的文章
怎样使用 CCache 进行 cocos2d-x 编译加速
查看>>
View的setTag()与getTag()方法使用
查看>>
2009年云数据库的开发和应用前景(转载)
查看>>
咏南中间件更新日志
查看>>
在rem布局下使用背景图片以及sprite
查看>>
JAVA设计模式之【抽象工厂模式】
查看>>
数字电视的电子节目指南(EPG)及其系统
查看>>
11 复用与多址
查看>>
附录A 编译安装Hadoop
查看>>
android studio building project info 错误
查看>>
【Scala】Scala之Control Structures
查看>>
三星手机拍照,从图库选择照片旋转问题完美解决
查看>>
算法笔记_173:历届试题 斐波那契(Java)
查看>>
菜鸟版JAVA设计模式—外观模式
查看>>
EasyUI----动态拼接EasyUI控件
查看>>
PHP session 跨子域问题总结 ini_set('session.cookie_domain', ".domain.com")
查看>>
Office WPS如何在页眉页脚添加一条横线
查看>>
站在 Android 开发的角度,聊聊 Airbnb 的 Lottie!!!
查看>>
数组去重Demo引出的思考
查看>>
javascript怎么禁用浏览器后退按钮
查看>>