社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python gevent 是如何 patch 标准库的 ?

hezhiming • 7 年前 • 373 次点击  

前言

使用 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://www.jintiankansha.me/t/J8epdPWypW
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/7584
 
373 次点击