简体   繁体   English

覆盖`import`以进行更复杂的模块导入

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

Is it possible to somehow override import so that I can do some more sophisticated operations on a module before it gets imported?是否有可能以某种方式覆盖import以便我可以在导入之前对模块进行一些更复杂的操作?

As an example: I have a larger application that uses matplotlib for secondary features that are not vital for the overall functionality of the application.举个例子:我有一个更大的应用程序,它使用matplotlib作为辅助功能,这些功能对应用程序的整体功能并不重要。 In case that matplotlib is not installed I just want to mock the functionality so that the import and all calls to matplotlib functions appear to be working, just without actually doing anything.如果未安装matplotlib我只想模拟该功能,以便导入和所有对matplotlib函数的调用似乎都在工作,只是实际上没有做任何事情。 A simple warning should then just indicate that the module is not installed, though that fact would not impair the core functionality of the application.一个简单的警告应该只是表明该模块未安装,尽管这一事实不会损害应用程序的核心功能。 I already have an import function that, in the case that matplotlib is not installed, returns a MagicMock object instead of the actual module which just mimics the behavior of the matplotlib API.我已经有一个导入函数,在没有安装matplotlib的情况下,它返回一个MagicMock对象而不是实际的模块,它只是模仿matplotlib API 的行为。

So, all import matplotlib... or from matplotlib import... should then be automatically overridden by the corresponding function call.因此,所有import matplotlib...from matplotlib import...都应该被相应的函数调用自动覆盖。 I could replace all import and from ... import expressions by hand but I'd like to make but there are a lot of them.我可以手动替换所有importfrom ... import表达式,但我想制作但有很多。 I'd rather like to have this functionality automatically by overriding import .我宁愿通过覆盖import自动拥有此功能。

Is that possible?那可能吗?

The easiest way I've found is to replace the __import__ function with your own implementation (described here ). 我发现最简单的方法是用你自己的实现替换__import__函数( 这里描述)。 Then if someone tries to import matplotlib , you just import a different module instead: 然后,如果有人尝试导入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

To restrict the custom import behavior to only a few modules, you can use introspection (with the inspect module ) to find out from which module the import has been performed: 要将自定义导入行为限制为仅少数模块,可以使用内省(使用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)

Short answer is NO ... But you could and should catch ImportError for when the module is not there, and handle it then. 简短的回答是否定的 ......但是你可以并且应该在模块不存在时捕获ImportError ,然后处理它。 Otherwise replacing all import statements with something else is the clever thing to do. 否则用其他东西替换所有import语句是很聪明的事情。

For a truly sophisticated import (adding functionality, hooking, etc.) you can define a meta hook and override whatever portion of the import behavior that you need.对于真正复杂的导入(添加功能、挂钩等),您可以定义一个元挂钩并覆盖您需要的导入行为的任何部分。 Example override script:示例覆盖脚本:

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

then place in the same folder a script called 'test_script.py':然后在同一个文件夹中放置一个名为“test_script.py”的脚本:

my_print('test')

and run the override script, you'll see that it defines 'my_print' such that when test_script is executed that your custom print command is used.并运行脚本覆盖,你会看到它定义“my_print”这样当test_script执行时使用您的自定义打印命令。 You can customize essentially anything you want at this point (eg wrapping a library with custom command replacement), since the hook gives you full control over the import.此时,您基本上可以自定义任何您想要的内容(例如,使用自定义命令替换来包装库),因为挂钩使您可以完全控制导入。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM