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