0

我正在使用 Python 3 来模拟另一个工具提供的 Python 脚本接口。(在编写此工具的脚本时,您运行 Python 脚本,这可以import mytool访问脚本 API。)

我已经实现了这个工具公开的接口,并且想编写一个你可以调用的加载器,例如:

python run_mytool_script.py dump_menu_items.py

此加载器允许您在不实际安装该工具的情况下与该工具的某些部分功能进行交互。理想情况下,这应该允许我运行为该工具设计的现有脚本而无需修改。

run_mytool_script.py我期望的范围内:

  1. 初始化模拟脚本接口
  2. 准备导入钩子
  3. exec剧本dump_menu_items.py

但是,我不太清楚如何创建导入挂钩。我怎样才能安装一个钩子,以便我的模拟脚本界面像mytool脚本一样暴露出来import mytool

注意,模拟脚本接口必须在运行时初始化,所以安装一个名为的包mytool并不能解决问题。

4

2 回答 2

2

好吧,有几种方法可以做到这一点。让我们从最复杂的开始 - 完全动态创建mytool. 您可以使用该imp模块创建一个新模块,然后定义其结构,最后将其添加到全局模块列表中,以便在同一解释器堆栈中运行的所有内容都可以导入它:

run_mytool_script.py

import imp
import sys

# lets first deal with loading of our external script so we don't waste cycles if a script
# was not provided as an argument
pending_script = None  # hold a script path we should execute
if __name__ == "__main__":  # make sure we're running this script directly, not imported
    if len(sys.argv) > 1:  # we need at least one argument to tell us which script to run
        pending_script = sys.argv[1]  # set the script to run as the first argument
    else:
        print("Please provide a path for a script to run")  # a script was not provided
        exit(1)

# now lets create the `mytool` module dynamically
mytool = imp.new_module("mytool")  # create a new module

def hello(name):  # define a local function
    print("Hello {}!".format(name))
mytool.__dict__["hello"] = hello  # add the function to the `mytool` module

sys.modules["mytool"] = mytool  # add 'mytool' to the global list so it can be imported

# now everything running in the same Python interpreter stack is able to import mytool
# lets run our `pending_script` if we're running the main script
if pending_script:
    try:
        execfile(pending_script)  # run the script
    except NameError:  # Python 3.x doesn't have execfile, use the exec function instead
        with open(pending_script, "r") as f:
            exec(f.read())  # read and run the script

mytool现在您可以创建另一个脚本,该脚本在通过代理脚本运行时相信您有一个模块,例如:

dump_menu_items.py

import mytool
mytool.hello("Dump Menu Items")

现在当你运行它时:

$ python run_mytool_script.py dump_menu_items.py
Hello Dump Menu Items!

虽然很简洁,但这是一项相当乏味的任务——我建议你mytool在同一个文件夹中创建一个合适的模块run_mytool_script.pyrun_mytool_script.py初始化所有需要的东西,然后使用脚本的最后一部分来运行你的外部脚本——就这么多了更易于管理,通常是一种更好的方法。

于 2017-07-06T19:38:16.310 回答
0

按照此处的 cPython 测试示例,我提出了以下潜在解决方案。当我意识到它的优点和缺点时,我会更新这篇文章。

class HookedImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader):
    def __init__(self, hooks=None):
        self.hooks = hooks

    def find_spec(self, name, path, target=None):
        if name not in self.hooks:
            return None

        spec = importlib.util.spec_from_loader(name, self)
        return spec

    def create_module(self, spec):
        # req'd in 3.6
        logger.info('hooking import: %s', spec.name)
        module = importlib.util._Module(spec.name)
        mod = self.hooks[spec.name]
        for attr in dir(mod):
            if attr.startswith('__'):
                continue
            module.__dict__[attr] = getattr(mod, attr)
        return module

    def exec_module(self, module):
        # module is already loaded (imported by line `import idb` above),
        # so no need to re-execute.
        #
        # req'd in 3.6.
        return

    def install(self):
        sys.meta_path.insert(0, self)

...稍后在某个地方...

api = mytool.from_config(...)
hooks = {
    'mytool': api.mytool,
}

importer = HookedImporter(hooks=hooks)
importer.install()


with open(args.script_path, 'rb') as f:
    g = {
        '__name__': '__main__',
    }
    g.update(hooks)
    exec(f.read(), g)
于 2017-07-06T19:37:57.897 回答