使用 Python 的人都知道,Python 世界有 gevent 這麼個協程庫,既優雅(指:接口比較不錯),性能又不錯,在對付 IO bound 的程序時,不失爲一個比較好的解決方案。bash
在使用 gevent 時,有一步是 patch 標準庫,即:gevent 對標準庫中一些同步阻塞調用的接口,本身進行了從新實現,而且讓應用層對標準庫的相關接口調用,所有重定向 gevent 的實現,以達到全異步的效果。 這一步比較有意思,讓人不由對其實現感到好奇,由於這種 patch 徹底是在後臺默默進行的,應用層根本不知道。若是咱們想實現看某個接口不慣,本身想替換它,可是又不想應用層代碼感知到 的效果,徹底能夠借鑑 gevent 的作法。框架
先是 Google 了一番,沒有搜到滿意的結果,看來還得本身親自看代碼。這篇文章便是記錄了對應的探索歷程。異步
gevent 有個接口的簽名以下:socket
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 的實現。ide
按照這種推測,理論上能夠對全部看不慣的庫動手腳,無論是標準庫,仍是第三方庫。函數
咱們由入口進,首先便看到以下代碼(爲了便於觀看,去掉了註釋和一些邊緣邏輯代碼):工具
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 的邏輯以下:ui
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 :this
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
複製代碼