【问题标题】:How do I load a Python module with custom globals using importlib?如何使用 importlib 加载带有自定义全局变量的 Python 模块?
【发布时间】:2017-07-21 09:34:59
【问题描述】:

我正在尝试用 Python 构建一个小型构建系统,为我的 C++ 项目生成 Ninja 文件。它的行为应该类似于 CMake;也就是说,bldfile.py 脚本定义规则和目标,并通过调用bld.subdir() 选择性地递归到一个或多个目录。每个bldfile.py 脚本都有一个对应的bld.File 对象。当bldfile.py 脚本正在执行时,bld 全局应该被预定义为该文件的bld.File 实例,但只能在该模块的范围内

此外,我想以某种方式利用 Python 的字节码缓存,但 .pyc 文件应存储在构建输出目录中,而不是与 bldfile.py 脚本一起存储在 __pycache__ 目录中。

我知道我应该使用importlib(需要 Python 3.4+ 就可以了),但我不知道如何:

  1. 使用自定义全局变量加载并执行模块文件。
  2. 重用字节码缓存基础设施。

任何帮助将不胜感激!

【问题讨论】:

  • 也许我误解了你,但 Python 的“全局变量”实际上已经是模块范围的 - 每个脚本的 bld 全局变量将自动不同(除非他们从另一个模块显式导入它,或者存储它进入另一个模块)。
  • 我想通了,但我不是一个 Python 程序员,所以我只是想明确这个要求,以防它对答案有任何影响。
  • 如果说自定义全局变量,是不是要在导入模块前设置好?
  • @kazemakase 是的,我希望它们在模块代码执行时可用。
  • @DavidBrown 我不认为这是可能的,但看看有人如何证明我错了会很有趣。当然有更简单的方法来实现您想要的功能,而无需入侵导入。例如,您可以在导入完成后执行所有工作的模块中定义入口函数。关于__pycache__,我认为是hard-coded,尽管定义似乎没有使用。现在无法详细说明进口问题的答案;明天再来看看。

标签: python python-module python-importlib


【解决方案1】:

在执行之前将全局变量注入模块是一个有趣的想法。但是,我认为它与Zen of Python 的几点冲突。特别是,它需要在模块中编写代码,这些代码依赖于未显式定义、导入或以其他方式获得的全局值 - 除非您知道调用模块所需的特定过程。

对于特定用例,这可能是一个明显或巧妙的解决方案,但不是很直观。一般来说,(Python)代码应该是显式的。因此,我会寻求一种解决方案,将参数显式传递给执行代码。听起来像函数?右:

bldfile.py

def exec(bld):
    print('Working with bld:', bld)
    # ...

调用模块:

# set bld

# Option 1: static import
import bldfile
bldfile.exec(bld)

# Option 2: dynamic import if bldfile.py is located dynamically
import importlib.util
spec = importlib.util.spec_from_file_location("unique_name", "subdir/subsubdir/bldfile.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
module.exec(bld)

这样在导入模块时不会执行任何代码(除了函数定义)。 exec 函数需要显式调用,当查看 exec 内部的代码时,很清楚 bld 的来源。

【讨论】:

  • 谢谢!我会接受这个答案,即使这不是我所采用的解决方案,因为这可能是大多数项目应该 做的(事实上,这就是 WAF 所做的)。在我的例子中,我希望bldfile.py 在最简单的情况下看起来更像一个配置文件而不是脚本文件。例如,编译一个库应该像一行一样简单:bld.lib('foo', 'foo.cc')。如果我决定让这个工具在我的 C++ 项目之外可用,我可能会重构它以使用你的方法。
【解决方案2】:

我研究了importlib 的源代码,因为我不打算制作一个可重复使用的Loader,所以这似乎有很多不必要的复杂性。所以我决定用types.ModuleType创建一个模块,将bld添加到模块的__dict__,用compile编译和缓存字节码,用exec执行模块。在低级别,这基本上就是importutil 所做的一切。

【讨论】:

  • 有趣的解决方案。我不知道你到底做了什么,但你可能不需要使用ModuleType。在代码对象上调用exec 时,您可以将bld 直接添加到全局变量中。 exec(code, {'bld': bld})
【解决方案3】:

可以通过使用虚拟模块来克服这种可能性,该模块将加载其全局变量。

#service.py 
module = importlib.import_module('userset')
module.user = user 
module = importlib.import_module('config')

#config.py
from userset import *
#now you can use user from service.py 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-01
    • 2014-12-01
    • 1970-01-01
    • 2018-04-22
    • 2017-02-04
    • 2021-06-26
    • 2015-01-02
    相关资源
    最近更新 更多