我发现最近总是跟一些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