我怎样才能使 Python "import.util.module_from_spec" 更像 "import""?

How can I make Python "import.util.module_from_spec" work more like "import""?

我正在继续 Ned Batchelder 的 byterun 的代码,这是一个用 Python 编写的 Python 解释器,适用于 Python 版本而不是 Python 3.4。参见 x-python

这种方法长期存在的问题之一是将导入中的解释器命名空间与解释程序命名空间分开。

旁白:如果您想要不解释导入模块的快速解释器,那么不分离名称空间可能是有利的,但是分离模块更正确,虽然速度较慢,并且在从不同的解释字节码时是必要的Python版本。

所以当解释器遇到 IMPORT_NAME 操作码时,我想使用 importlib.util 来基本上拥有一个 copy 模块,它不同于解释器遇到的任何导入。

我现在遇到的问题是这些导入方式不同,可以使用 hasattr().

查看

这是一个例子:

import importlib

module_spec = importlib.util.find_spec("textwrap")
textwrap_module = importlib.util.module_from_spec(module_spec)
submodule = "fill"
print(hasattr(textwrap_module, submodule)) # False

import textwrap
print(hasattr(textwrap, submodule)) # True

如何使用 importlib.util 获得相同的行为?

(但我应该注意,对于 sys,两者都可以找到“路径”子模块作为 sys 的属性。)

问题出在 importlib。由于某种原因,它没有加载所有模块属性。

让我们来看看以下案例:

案例 01 - 代码:

import importlib


module_spec = importlib.util.find_spec("textwrap")
textwrap_module = importlib.util.module_from_spec(module_spec)
submodule = "fill"
print(f'Attributes: {dir(textwrap_module)}')
print(f'Attribute Found: {hasattr(textwrap_module, submodule)}')

案例 01 - 输出:

Attributes: ['__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
Attribute Found: False

如您所见,在属性列表中,没有名为 fill 的属性。这就是为什么 returns False.

案例02 - 代码:

import textwrap


print(f'Attributes: {dir(textwrap)}')
print(f'Attribute Found: {hasattr(textwrap, submodule)}') # True

案例 02 - 输出:

Attributes: ['TextWrapper', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_leading_whitespace_re', '_whitespace', '_whitespace_only_re', 'dedent', 'fill', 'indent', 're', 'shorten', 'wrap']
Attribute Found: True

在这种情况下,您可以在属性列表中看到一个名为 fill 的属性。这就是为什么 returns True.

这背后的原因

# Bootstrap help #####################################################

# Until bootstrapping is complete, DO NOT import any modules that attempt
# to import importlib._bootstrap (directly or indirectly). Since this
# partially initialised package would be present in sys.modules, those
# modules would get an uninitialised copy of the source version, instead
# of a fully initialised version (either the frozen one or the one
# initialised below if the frozen one is not available).

结论:

通过分析以上两种情况,我们可以说由于上述原因,通过importlib导入模块与内置import语句的工作方式不同。

模块是否执行过?如果不是,则模块内的赋值或声明还没有将子对象放入模块对象的__dir__中。从标准文件加载器中此函数的版本来看,当您调用 module_from_spec 时,您得到的只是模块,以及逻辑上属于所有模块的成员。没有内容。

(系统可能会在不同的路径上加载模块或与其他 Loader 对象处于不同的使用状态,这可能会使整个任务复杂化。例如,对于 sys.path,您获得的模块似乎已填充已经。请记住,这里有一个子对象是有原因的。不只是一个 Python 加载器。)

如果尚未加载,要填充模块对象,您将调用

module_spec.loader.exec_module(module)

(如果存在的话)

例如 Python 3.8:

import importlib
module_spec = importlib.util.find_spec('textwrap')
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
print(module.fill)

将输出:

<function fill at 0x7fae70e6fd90>

在旧版本的Python中,缺少exec_module,您需要调用:

module_spec.loader.load_module(module.__name__)

然而,在 3.6 及更高版本上,上面的代码按预期工作。

信息是通过查看 importlib._bootstrap.py::_exec 及其调用的内容收集的。

这里还有一个问题,即加载模块中的导入最终会出现在全局名称空间中,还是您自己的名称空间中。但那是另一个问题。