簡體   English   中英

覆蓋`import`以進行更復雜的模塊導入

[英]Override `import` for more sophisticated module import

是否有可能以某種方式覆蓋import以便我可以在導入之前對模塊進行一些更復雜的操作?

舉個例子:我有一個更大的應用程序,它使用matplotlib作為輔助功能,這些功能對應用程序的整體功能並不重要。 如果未安裝matplotlib我只想模擬該功能,以便導入和所有對matplotlib函數的調用似乎都在工作,只是實際上沒有做任何事情。 一個簡單的警告應該只是表明該模塊未安裝,盡管這一事實不會損害應用程序的核心功能。 我已經有一個導入函數,在沒有安裝matplotlib的情況下,它返回一個MagicMock對象而不是實際的模塊,它只是模仿matplotlib API 的行為。

因此,所有import matplotlib...from matplotlib import...都應該被相應的函數調用自動覆蓋。 我可以手動替換所有importfrom ... import表達式,但我想制作但有很多。 我寧願通過覆蓋import自動擁有此功能。

那可能嗎?

我發現最簡單的方法是用你自己的實現替換__import__函數( 這里描述)。 然后,如果有人嘗試導入matplotlib ,您只需導入另一個模塊:

def _import(name, *args, **kwargs):
    if name == 'matplotlib': # if someone tries to import matplotlib...
        name = 'my_mocked_matplotlib' # ...import the mocked version instead
    return original_import(name, *args, **kwargs)

import builtins
original_import = builtins.__import__
builtins.__import__ = _import

要將自定義導入行為限制為僅少數模塊,可以使用內省(使用inspect模塊 )查找導入執行的模塊:

import inspect

def _import(name, *args, **kwargs):
    if name == 'matplotlib': # if someone tries to import matplotlib...
        # find out which module is performing the import
        frame = inspect.currentframe().f_back
        module_path = frame.f_globals['__file__']

        # if the import is happening in module1 or module2, redirect it
        if module_path in ('/path/to/module1.py','/path/to/module2.py'):
            name = 'my_mocked_matplotlib' # ...import the mocked version instead

    return original_import(name, *args, **kwargs)

簡短的回答是否定的 ......但是你可以並且應該在模塊不存在時捕獲ImportError ,然后處理它。 否則用其他東西替換所有import語句是很聰明的事情。

對於真正復雜的導入(添加功能、掛鈎等),您可以定義一個元掛鈎並覆蓋您需要的導入行為的任何部分。 示例覆蓋腳本:

from os.path import (dirname, abspath, join, splitext)
from os import (listdir)
import importlib.util
import imp
import sys
_scripts_path = dirname(abspath(__file__))
_script_list = [splitext(f)[0] for f in listdir(_scripts_path) if splitext(f)[1] == '.py']

class custom_import_hook(object):
    def find_module(self, name, path):
        if name not in _script_list: return None
        return self
    
    class _cmds(object):
        @staticmethod
        def my_print(string):
            print(string)
    
    def load_module(self, name):
        sys.modules.setdefault(name, imp.new_module(name))
        spec = importlib.util.spec_from_file_location('module.name', join(_scripts_path, f'{name}.py'))
        foo = importlib.util.module_from_spec(spec)
        for cmd in self._cmds.__dict__.keys():
            if cmd[0] == '_': continue
            setattr(foo, cmd, getattr(self._cmds, cmd))
        sys.meta_path.append(self)
        spec.loader.exec_module(foo)
        sys.meta_path.remove(self)
        return foo

meta_hook = custom_import_hook()
sys.meta_path.insert(0, meta_hook)
import test_script

然后在同一個文件夾中放置一個名為“test_script.py”的腳本:

my_print('test')

並運行腳本覆蓋,你會看到它定義“my_print”這樣當test_script執行時使用您的自定義打印命令。 此時,您基本上可以自定義任何您想要的內容(例如,使用自定義命令替換來包裝庫),因為掛鈎使您可以完全控制導入。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM