源码分析之gevent monkey.patch_all实现原理

我发现最近总是跟一些python的标准库及第三方的库干上了。 平时没怎么关注,但只要一遇到问题,就想刨根问底分析源代码 ! 这样不仅解决当前问题,还能学习下功能模块是怎么实现的,另外还能学习别人的coder黑技巧.

该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新。 http://xiaorui.cc/?p=3248

前端时间写了一组连载的技术文章,主要是关于web框架的构成,别看标题有点大,内容虽然也有实质,但自己感觉还是不够深。  就这样web框架先暂停一个段落。

聊聊python的异步库gevent如何打猴子补丁, 他的用途是让你方便的导入非阻塞的模块,不需要特意的去引入。  我们暂时抛开gevent 猴子补丁代码实现,如果我们自己要实现module的替换, 可以说是” 模块补丁 ” 该怎么实现 ?  

第一种方法:
第一步, 直接干掉del sys.modules[xxx]  #可以省掉
第二步, __import__(xiaorui.cc)
第三步,sys.modules[xiaorui.cc] = __import__(xiaorui.cc)

文件: Mod_1.py

def test_function():
    print "Test Function -- Mod 1"

文件: Mod_2.py

def test_function():
    print "Test Function -- Mod 2"


文件: xiaorui.py

import sys
import Mod_1

Mod_1.test_function()

del sys.modules['Mod_1']

sys.modules['Mod_1'] = __import__('Mod_2')

import Mod_1

Mod_1.test_function()

结果跟我们预想的一样:

Test Function -- Mod 1
Test Function -- Mod 2

第二种:  偏软,显得不是很暴力:

#xiaorui.cc
import json
import ujson
def monkey_patch_json():
    json.__name__ = 'ujson'
    json.dumps = ujson.dumps
    json.loads = ujson.loads

monkey_patch_json()
print 'main.py',json.__name__

上面的是咱们自定义模块替换方法,那么我们绕回主题 gevent pathc_all() 

#xiaorui.cc
import threading
from gevent import monkey; monkey.patch_all()

def demo():
	print "hello"

class Xiaorui(threading.Thread):
    def run(self):
        demo()
        print 'finished working'

if __name__ == '__main__':
    worker = Xiaorui()
    worker.start()
    print 'finished'

上面的实例代码中我们有用到gevent monkey的补丁.  当我们执行patch_all()的时候,默认会把猴子所能支持的模块都打了补丁.

如果你不想打入threading的补丁怎么办 ? 解决的办法很简单,要不就是在gevent.monkey.patch_all(thread=False) , 要不就在patch_all后面追加import threading ,目的是覆盖前面的引入.

#xiaorui.cc
#!/usr/bin/env python
from gevent import monkey, sleep
monkey.patch_all()
#monkey.patch_all(thread=False)
import threading

class Xiaorui(threading.Thread):
    def run(self):
        for i in xrange(10):
            print 'working'
            sleep()

if __name__ == '__main__':
    worker = Xiaorui()
    worker.start()
    print 'finished'

那么当我们执行gevent.monkey.pathc_all()的时候,gevent做了什么?

我们发现gevent patch_all针对模块的引入是有顺序的,另外文档也标明 ,这order很重要 ! 重要的原因是他们之间有依赖的关系。  举个简单的例子,subprocess是需要sys,os的支持的,如果sys.Popen是阻塞的,那么subprocess必阻塞.

#xiaorui.cc
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, _warnings=_warnings)
    # 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()

    ... 省略 (from xiaorui.cc)
    ... 省略 (from xiaorui.cc)

这个函数的作用是python locals()当前环境中的变量赋值到saved字典里面.

def _check_repatching(**module_settings):
    _warnings = []
    key = '_gevent_saved_patch_all'
    if saved.get(key, module_settings) != module_settings:
        _queue_warning("Patching more than once will result in the union of all True"
                       " parameters being patched",
                       _warnings)

    first_time = key not in saved
    saved[key] = module_settings
    return _warnings, first_time
#导入os

def patch_os():
    patch_module('os')

#导入gevent.sleep

def patch_time():
    """Replace :func:`time.sleep` with :func:`gevent.sleep`."""
    from gevent.hub import sleep
    import time
    patch_item(time, 'sleep', sleep)


通过getattr自省模式创建一个对象,然后通过__import__引入。

注意:
__import__跟import语句实现的功能是相同的,但__import__是一个函数,并且只接收字符串作为参数,所以它的作用就可想而知了。其实import语句就是调用这个函数进行导入工作的,import sys <==>sys = __import__(‘sys’) 。 通常在动态加载时可以使用到这个函数,比如你希望加载某个文件夹下的所用模块,但是其下的模块名称又会经常变化时,就可以使用这个函数动态加载所有模块了,最常见的场景就是插件功能的支持。

#xiaorui.cc
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做可执行对象关联.

#xiaorui.cc
def patch_item(module, attr, newitem):
    olditem = getattr(module, attr, _NONE)
    if olditem is not _NONE:
        saved.setdefault(module.__name__, {}).setdefault(attr, olditem)
    setattr(module, attr, newitem)

翻弄了下Gevent monkey的源码发现没多少好讲的,因为场景的原因,gevent的monkey实现比起咱们自定义的来说稍显的复杂。  下篇给大家分享下 gevent  hub.py core.py的实现,最近看了gevent的源码有些心得。

END


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

邮箱地址不会被公开。 必填项已用*标注