要導入一組函數,同時導入一組函數的第二個實例,您可以覆蓋標準的Python導入掛鉤並在導入時直接應用這些修補程序。這將確保任何其他模塊都不會看到任何模塊的未打補丁版本,所以即使他們直接按名稱從另一個模塊導入函數,他們也只會看到已打補丁的函數。這是證明了概念的實現:
import __builtin__
import collections
import contextlib
import sys
@contextlib.contextmanager
def replace_import_hook(new_import_hook):
original_import = __builtin__.__import__
__builtin__.__import__ = new_import_hook
yield original_import
__builtin__.__import__ = original_import
def clone_modules(patches, additional_module_names=None):
"""Import new instances of a set of modules with some objects replaced.
Arguments:
patches - a dictionary mapping `full.module.name.symbol` to the new object.
additional_module_names - a list of the additional modules you want new instances of, without
replacing any objects in them.
Returns:
A dictionary mapping module names to the new patched module instances.
"""
def import_hook(module_name, *args):
result = original_import(module_name, *args)
if module_name not in old_modules or module_name in new_modules:
return result
# The semantics for the return value of __import__() are a bit weird, so we need some logic
# to determine the actual imported module object.
if len(args) >= 3 and args[2]:
module = result
else:
module = reduce(getattr, module_name.split('.')[1:], result)
for symbol, obj in patches_by_module[module_name].items():
setattr(module, symbol, obj)
new_modules[module_name] = module
return result
# Group patches by module name
patches_by_module = collections.defaultdict(dict)
for dotted_name, obj in patches.items():
module_name, symbol = dotted_name.rsplit('.', 1) # Only allows patching top-level objects
patches_by_module[module_name][symbol] = obj
try:
# Remove the old module instances from sys.modules and store them in old_modules
all_module_names = list(patches_by_module)
if additional_module_names is not None:
all_module_names.extend(additional_module_names)
old_modules = {}
for name in all_module_names:
old_modules[name] = sys.modules.pop(name)
# Re-import modules to create new patched versions
with replace_import_hook(import_hook) as original_import:
new_modules = {}
for module_name in all_module_names:
import_hook(module_name)
finally:
sys.modules.update(old_modules)
return new_modules
在這裏,這個實現一些測試代碼:
from __future__ import print_function
import math
import random
def patched_log(x):
print('Computing log({:g})'.format(x))
return math.log(x)
patches = {'math.log': patched_log}
cloned_modules = clone_modules(patches, ['random'])
new_math = cloned_modules['math']
new_random = cloned_modules['random']
print('Original log: ', math.log(2.0))
print('Patched log: ', new_math.log(2.0))
print('Original expovariate: ', random.expovariate(2.0))
print('Patched expovariate: ', new_random.expovariate(2.0))
測試代碼有這樣的輸出:
Computing log(4)
Computing log(4.5)
Original log: 0.69314718056
Computing log(2)
Patched log: 0.69314718056
Original expovariate: 0.00638038735379
Computing log(0.887611)
Patched expovariate: 0.0596108277801
前兩個來自these two lines in random
的輸出結果行在輸入時執行。這表明random
立即看到了修補功能。其餘輸出表明原始的math
和random
仍然使用未修補的版本log
,而克隆的模塊都使用修補版本。
覆蓋進口鉤可能是使用一個元進口吊鉤在PEP 302定義,而是提供了一個全面推行這一做法的的更清潔的方式是超越的StackOverflow的範圍。
這聽起來像是你會遇到像循環導入混亂你的漂亮的拓撲秩序和C模塊不能調用你的替代函數的問題。 – user2357112
我沒有C模塊調用Python函數,所以只能看看克隆Python函數。 Python本身如何處理循環導入? (是不是一個錯誤?) –
循環進口並不是天生的錯誤,但他們通常是一個壞主意。如果循環導入導致您嘗試在初始化過程中導入模塊,則會獲得半初始化模塊對象。這意味着從進口和依賴於初始化模塊的其他東西將不起作用。 – user2357112