Monkey Patch(猴子補丁) 是指在運行時動態修改已有模塊、類或函數的行為,以實現熱修復、行為擴展或替換標準庫組件。Python 的動態特性(命名空間基於字典)使其極易實現。
class Foo:
def bar(self):
print('Foo.bar')
def new_bar(self):
print('Modified bar')
Foo.bar = new_bar # 運行時替換類方法
Foo().bar() # 輸出: Modified bar
Python 有三層命名空間查找順序:
__builtins__)模塊導入時,會將模塊對象放入 sys.modules 緩存,並將其公共符號注入調用者的 globals。
Eventlet 利用 Monkey Patch 將標準庫中的阻塞 I/O(如 socket、threading、time)替換為協程友好的綠色版本,實現「無感知異步化」。
inject()def inject(module_name, new_globals, *additional_modules):
"""
將綠色模塊注入目標模塊
:param module_name: 目標模塊名(如 'ftplib')
:param new_globals: 目標模塊的 globals 字典(用於填充綠色符號)
:param additional_modules: (name, module) 元組列表,指定要替換的標準庫模塊
"""
# 1. 保存原模塊並臨時替換 sys.modules
saved = {}
for name, mod in additional_modules:
saved[name] = sys.modules.get(name)
sys.modules[name] = mod # 如用 eventlet.green.socket 替換 socket
# 2. 移除舊模塊並重新導入(觸發 import 機制使用新模塊)
old_module = sys.modules.pop(module_name, None)
try:
module = __import__(module_name, {}, {}, module_name.split('.')[:-1])
# 3. 將新模塊的所有公共符號注入 new_globals
if new_globals is not None:
for name in dir(module):
if name not in __exclude: # 排除 __builtins__ 等
new_globals[name] = getattr(module, name)
# 保持引用防止 GC
sys.modules['__patched_module_' + module_name] = module
finally:
# 4. 恢復原模塊
if old_module is not None:
sys.modules[module_name] = old_module
elif module_name in sys.modules:
del sys.modules[module_name]
# 5. 恢復所有臨時替換的模塊
for name, mod in additional_modules:
if saved[name] is not None:
sys.modules[name] = saved[name]
else:
del sys.modules[name]
return module
| 步驟 | 操作 | 目的 |
|---|---|---|
| 1 | 替換 sys.modules 中的標準庫模塊 | 讓後續 import 獲取綠色版本 |
| 2 | __import__ 重新導入目標模塊 | 目標模塊導入時會使用已替換的綠色依賴 |
| 3 | 手動將符號注入 new_globals | 繞過 from x import * 的 __all__ 限制,實現完全注入 |
| 4 | finally 恢復原狀 | 隔離影響範圍,不污染全局環境 |
from ftplib import *?因為 from ... import * 受 __all__ 控制,無法導入以下劃線開頭的私有符號,無法實現完全替換。手動遍歷 dir(module) 可獲取所有屬性。
Eventlet 的 Patcher 展示了 Python 動態特性的極致應用:通過精確控制 sys.modules 導入機制與命名空間注入,實現了對標準庫的無感知綠色化改造。理解此機制,有助於深入掌握 Python 導入系統、命名空間與動態編程精髓。