如何实现一个可以使用importlib即时修改源代码的导入挂钩? [英] How to implement an import hook that can modify the source code on the fly using importlib?

查看:188
本文介绍了如何实现一个可以使用importlib即时修改源代码的导入挂钩?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用不推荐使用的模块imp,我可以编写一个自定义的导入钩子,该钩子可以在通过Python导入/执行之前即时修改模块的源代码.在下面给出源代码为名为source的字符串的情况下,创建模块所需的基本代码如下:

Using the deprecated module imp, I can write a custom import hook that modifies the source code of a module on the fly, prior to importation/execution by Python. Given the source code as a string named source below, the essential code needed to create a module is the following:

module = imp.new_module(name)
sys.modules[name] = module
exec(source, module.__dict__)

由于不推荐使用imp,所以我想对importlib做类似的事情.

Since imp is deprecated, I would like to do something similar with importlib.

但是,我还无法弄清楚该怎么做. importlib文档具有,据我所知,该模块是包含其自身的对象加载程序没有明显的方法来重新定义它们,以便能够从字符串创建模块.

However, I have not been able to figure out how to do this. The importlib documentation has a function to create modules from "specs" which, as far as I can tell, are objects that include their own loaders with no obvious way to redefine them so as to be able to create a module from a string.

我创建了一个最小示例进行演示;有关详细信息,请参见自述文件.

I have created a minimal example to demonstrates this; see the readme file for details.

推荐答案

find_moduleload_module均已弃用.您需要分别切换到find_spec和(create_moduleexec_module)模块.有关详细信息,请参见importlib 文档.

find_module and load_module are both deprecated. You'll need to switch to find_spec and (create_module and exec_module) module respectively. See the importlib documentation for details.

您还需要检查是否要使用MetaPathFinderPathEntryFinder,因为调用它们的系统不同.也就是说,元路径查找器首先运行并且可以覆盖内置模块,而路径条目查找器专门用于在sys.path上找到的模块.

You will also need to examine if you want to use a MetaPathFinder or a PathEntryFinder as the system to invoke them is different. That is, the meta path finder goes first and can override builtin modules, whereas the path entry finder works specifically for modules found on sys.path.

以下是一个非常基本的进口商,试图替换整个进口机器.它显示了如何使用功能(find_speccreate_moduleexec_module).

The following is a very basic importer that attempts to replace the entire import machinery for. It shows how to use the functions (find_spec, create_module, and exec_module).

import sys
import os.path

from importlib.abc import Loader, MetaPathFinder
from importlib.util import spec_from_file_location

class MyMetaFinder(MetaPathFinder):
    def find_spec(self, fullname, path, target=None):
        if path is None or path == "":
            path = [os.getcwd()] # top level import -- 
        if "." in fullname:
            *parents, name = fullname.split(".")
        else:
            name = fullname
        for entry in path:
            if os.path.isdir(os.path.join(entry, name)):
                # this module has child modules
                filename = os.path.join(entry, name, "__init__.py")
                submodule_locations = [os.path.join(entry, name)]
            else:
                filename = os.path.join(entry, name + ".py")
                submodule_locations = None
            if not os.path.exists(filename):
                continue

            return spec_from_file_location(fullname, filename, loader=MyLoader(filename),
                submodule_search_locations=submodule_locations)

        return None # we don't know how to import this

class MyLoader(Loader):
    def __init__(self, filename):
        self.filename = filename

    def create_module(self, spec):
        return None # use default module creation semantics

    def exec_module(self, module):
        with open(self.filename) as f:
            data = f.read()

        # manipulate data some way...

        exec(data, vars(module))

def install():
    """Inserts the finder into the import machinery"""
    sys.meta_path.insert(0, MyMetaFinder())

接下来是一个稍微精致的版本,它试图重用更多的导入机制.这样,您只需要定义如何获取模块的源即可.

Next is a slightly more delicate version that attempts to reuse more of the import machinery. As such, you only need to define how to get the source of the module.

import sys
from os.path import isdir
from importlib import invalidate_caches
from importlib.abc import SourceLoader
from importlib.machinery import FileFinder


class MyLoader(SourceLoader):
    def __init__(self, fullname, path):
        self.fullname = fullname
        self.path = path

    def get_filename(self, fullname):
        return self.path

    def get_data(self, filename):
        """exec_module is already defined for us, we just have to provide a way
        of getting the source code of the module"""
        with open(filename) as f:
            data = f.read()
        # do something with data ...
        # eg. ignore it... return "print('hello world')"
        return data


loader_details = MyLoader, [".py"]

def install():
    # insert the path hook ahead of other path hooks
    sys.path_hooks.insert(0, FileFinder.path_hook(loader_details))
    # clear any loaders that might already be in use by the FileFinder
    sys.path_importer_cache.clear()
    invalidate_caches()

这篇关于如何实现一个可以使用importlib即时修改源代码的导入挂钩?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆