2016-05-17 55 views
9

我想製作一個完整的命名空間的副本,同時用動態構建的版本替換一些函數。製作整個名稱空間的副本?

換句話說,從名稱空間(import tensorflow as tf)開始,我想複製它,用我自己的版本替換一些函數,並更新所有符號的__globals__以保留在新的名稱空間中。這需要按照依賴關係的拓撲順序來完成。

我開始做類似here但現在我開始懷疑我是否在重新發明輪子。需要注意處理系統模塊中的循環依賴關係,功能/類型/對象需要以不同方式進行更新等。

任何人都可以指向解決類似任務的現有代碼嗎?

+0

這聽起來像是你會遇到像循環導入混亂你的漂亮的拓撲秩序和C模塊不能調用你的替代函數的問題。 – user2357112

+0

我沒有C模塊調用Python函數,所以只能看看克隆Python函數。 Python本身如何處理循環導入? (是不是一個錯誤?) –

+0

循環進口並不是天生的錯誤,但他們通常是一個壞主意。如果循環導入導致您嘗試在初始化過程中導入模塊,則會獲得半初始化模塊對象。這意味着從進口和依賴於初始化模塊的其他東西將不起作用。 – user2357112

回答

4

要導入一組函數,同時導入一組函數的第二個實例,您可以覆蓋標準的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立即看到了修補功能。其餘輸出表明原始的mathrandom仍然使用未修補的版本log,而克隆的模塊都使用修補版本。

覆蓋進口鉤可能是使用一個元進口吊鉤PEP 302定義,而是提供了一個全面推行這一做法的的更清潔的方式是超越的StackOverflow的範圍。

+0

:當我在tensorflow上嘗試這種方法時,由於它依賴'isinstance',它引起了問題。我從原始名稱空間傳入對象,因此'isinstance'返回false,因爲它與複製類型進行比較。因此,我不需要「複製所有內容」,而需要複製所需的最小集合以確保修補後的函數引用已成功更新,因此我回到了原始方法,並進行了額外的修改以處理閉包/默認值 –

1

在我看來,你可以很容易地做到這一點:

import imp, string 

st = imp.load_module('st', *imp.find_module('string')) # copy the module 

def my_upper(a): 
    return "a" + a 

def my_lower(a): 
    return a + "a" 

st.upper = my_upper 
st.lower = my_lower 


print string.upper("hello") # HELLO 
print string.lower("hello") # hello 

print st.upper("hello") # ahello 
print st.lower("hello") # helloa 

當你罵st.upper("hello"),它會導致"hello"

所以,你並不需要混淆全局。

+0

是的,這有效,但我實際上需要保留字符串的新功能和新功能,所以我需要複製一份。 IE瀏覽器,我不想改變每個用戶的「字符串」使用新版本的鞋面,但只改變它在一個特定的模塊。 –

+0

@YaroslavBulatov,沒問題,'my_string = st; my_string.upper = test'。編輯:不,這裏有一個問題,請等待... – ForceBru

+0

所以我有一個模塊的一些功能和依賴關係。我想複製這個模塊,以便在從它調用函數時,最終某些函數可能會調用「string.upper」,它會轉到定製版本。但是,還想保留對原始/未修補模塊的訪問權限,其中「上」功能保持不變 –

3

與其試圖製作模塊內容的副本並將其中的所有內容修補爲使用正確的全局變量,可能會誘騙Python導入您想要第二次複製的所有內容。這將爲您提供所有模塊的新初始化副本,因此它不會複製模塊可能具有的任何全局狀態(不確定是否需要這樣做)。

import importlib 
import sys 

def new_module_instances(module_names): 
    old_modules = {} 
    for name in module_names: 
     old_modules[name] = sys.modules.pop(name) 
    new_modules = {} 
    for name in module_names: 
     new_modules[name] = importlib.import_module(name) 
    sys.modules.update(old_modules) 
    return new_modules 

請注意,我們先刪除我們想要從sys.modules取代所有模塊,所以他們都得到導入第二次,而這些模塊之間的依賴關係自動正確設置。在函數結束時,我們恢復原始狀態sys.modules,所以其他一切都繼續看到這些模塊的原始版本。

下面是一個例子:

>>> import logging.handlers 
>>> new_modules = new_module_instances(['logging', 'logging.handlers']) 
>>> logging_clone = new_modules['logging'] 
>>> logging 
<module 'logging' from '/usr/lib/python2.7/logging/__init__.pyc'> 
>>> logging_clone 
<module 'logging' from '/usr/lib/python2.7/logging/__init__.pyc'> 
>>> logging is logging_clone 
False 
>>> logging is logging.handlers.logging 
True 
>>> logging_clone is logging_clone.handlers.logging 
True 

最後三個表達式顯示,記錄的兩個版本不同的模塊,而模塊handlers兩個版本使用logging模塊的正確版本。

+0

我確實不在乎模塊狀態。這個+常規monkeypatching似乎比我所做的更簡單,會試試看看是否有任何陷阱。 –

+0

似乎我仍然需要遞歸檢查模塊層次結構中的所有符號,因爲某些模塊可能已經使用'import myfunc as saved_copy'存儲對該函數的引用。 –

+0

@YaroslavBulatov:這是一個單獨的問題,並且從你的問題來看,你還不清楚你是否也需要解決這個問題。每當您想要在任何地方使用猴子修補功能時都會發生此問題,並且它不是特定於您的情況。但是,您的具體情況可能會比通常更容易解決此問題。該解決方案是否必須在Python 2和3中工作,還是隻能在Python 3中工作的解決方案也不錯? –

相關問題