IronPython - 有没有办法从数据库加载 python 脚本或从字符串资源嵌入?

IronPython - Is there a way to load a python script from the database or embedded from a string resource?

当 IronPython 试图 resolve/load 一个它找不到的模块时,有没有办法从 IronPython 中捕获某种 even?

我们想将模块存储在数据库中或将它们嵌入到 DLL 中并通过调用 import mymodule 导入它们。我们根本不想涉及文件系统。当我们说 import something 时,我们不希望它在 \Lib 或任何文件系统中查找。

以下代码可以很好地加载嵌入在 DLL 中的 Python 块。它仅在没有导入时有效。

    var myScript = new StreamReader(Assembly.GetAssembly(this.GetType()).GetManifestResourceStream("Resource.Name.To.My.Script")).ReadToEnd()
    var engine = IronPython.Hosting.Python.CreateEngine();
    var scope = engine.CreateScope();
    var result = engine.Execute(myScript, scope);

和 python 代码:

print('hello world')

如果有导入,则无法正常工作。它不知道如何解析模块的位置。

我能让它工作的唯一方法是确保我们需要的任何模块都对文件系统中的引擎可见。这涉及使用 engine.GetSearchPaths() (查看它正在寻找的路径)和使用 engine.SetSearchPaths() 附加任何其他搜索路径。但这些来自文件系统,这不是我想要的。

我想一个不错的方法可能是从引擎接收某种事件,例如 "OnLookingForModuleNamedXYZ(moduleName)"。然后我可以在数据库或 DLL 的嵌入式资源中查找模块并发回模块的字符串。

我该怎么做?

您需要添加自定义导入挂钩。 Python 添加了一种注册自定义导入挂钩的方法。在此机制之前,您必须覆盖 built-in __import__ 函数。但它特别 error-prone,尤其是当多个库都试图添加自定义导入行为时。

PEP 0302 详细描述了该机制。

基本上,您需要创建两个对象 -- 一个 Finder 对象和一个 Loader 对象。

Finder 对象应定义一个名为 find_module

的函数

This method will be called with the fully qualified name of the module. If the finder is installed on sys.meta_path , it will receive a second argument, which is None for a top-level module, or package.__path__ for submodules or subpackages [5] . It should return a loader object if the module was found, or None if it wasn't.

如果find_module函数找到模块和returns一个Loader对象,Loader对象应该定义一个load_module函数

This method returns the loaded module or raises an exception, preferably ImportError if an existing exception is not being propagated. If load_module() is asked to load a module that it cannot, ImportError is to be raised.

这是一个详细说明其工作原理的简短示例:

import sys
import imp

class CustomImportFinder(object):

    @staticmethod
    def find_module(fullname, path=None):
        # Do your custom stuff here, look in database, etc.
        code_from_database = "VAR = 1"
        return CustomImportLoader(fullname, path, code_from_database)


class CustomImportLoader(object):

    def __init__(self, fullname, path, code_from_database):
        super(CustomImportLoader, self).__init__()
        self.fullname = fullname
        self.path = path
        self.code_from_database = code_from_database

    def is_package(fullname):
        # is this a package?
        return False

    def load_module(self, fullname):
        code = self.code_from_database
        ispkg = self.is_package(fullname)
        mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
        mod.__file__ = "<%s>" % self.__class__.__name__
        mod.__loader__ = self
        if ispkg:
            mod.__path__ = []
            mod.__package__ = fullname
        else:
            mod.__package__ = fullname.rpartition('.')[0]
        exec(code, mod.__dict__)
        return mod

sys.meta_path.append(CustomImportFinder)

import blah
print(blah)
# <module 'blah' (built-in)>
print(blah.VAR)
# 1