【问题标题】:How can I make Python "import.util.module_from_spec" work more like "import""?如何让 Python “import.util.module_from_spec”更像“import”?
【发布时间】:2021-08-31 22:42:23
【问题描述】:

我将继续为 Ned Batchelder 的 byterun 编写代码,这是一个用 Python 编写的 Python 解释器,适用于 Python 3.4 以外的 Python 版本。见x-python

这种方法的一个长期关注点是将导入中的解释器命名空间与解释程序命名空间分开。

另外:如果您想要快速解释器而不解释为导入的模块,则不分离命名空间可能是有利的,但是在解释来自不同 Python 版本的字节码时,分离模块更正确,虽然速度较慢,并且是必要的。

因此,当解释器遇到IMPORT_NAME 操作码时,我想使用importlib.util 基本上拥有与解释器遇到的任何导入不同的模块的副本。 p>

我现在遇到的问题是这些导入方式不同,这可以使用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 的属性。)

【问题讨论】:

  • 您提到的代码的预期结果是什么?
  • @Sabil 我不确定你到底想要什么。如果运行程序,输出的第一行是 False,第二行是 True。我们想要发生的是当使用 importlib.util 时,子模块“fill”被列为导入的 textwrap 模块(和导入)的属性,就像使用 import 时所做的一样。
  • 我只是为你添加一个解释。希望能帮助到你。 :)

标签: python-3.x python-import python-importlib


【解决方案1】:

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

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

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

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 以及它调用的东西收集的。

这里仍然存在一个问题,即加载模块中的导入最终会在全局命名空间中还是您自己的命名空间中。但这是另一个问题。

【讨论】:

  • 使用这个是用Python写的Python解释器。因此,当编写“IMPORT_NAME”操作码时,会调用 importlib 例程并返回模块。该模块被推入堆栈。稍后(大概)一些其他 Python 字节码操作会弹出它,也许稍后它会查看该模块的属性。我发现如果我保存从importlib.util.find_spec(name) 返回的“规范”,那么我可以在spec.loader.load_module(name)spec.loader.exec_module(module) 中使用它来模拟boostrap 的作用。
  • 您通过执行这两个附加行来复制与导入相同的行为。您可以通过将它们放在正确的位置并使用导入机制的其余部分并使用正确连接的加载器来自动调用它们。如果由于上下文而无法使用该机制的其余部分,则您只需要自己设法调用它们即可。
  • 问题或奇怪之处在于,这感觉有点像魔术。事实上,我从 Python 3.8 的 _bootstrap_external.py 中的 _exec(spec, module) 复制了这些行,避免引用与此处无关的 sys.module。并且该代码以一种不稳定的方式工作,指的是问题 19713。这并没有给我一个好的感觉。我对 Python 导入的经验是,其中仍然存在一些尴尬,可能需要进行额外的工作以使 API 更易于使用且不会让人感到尴尬。
  • 但是如果您查看 module_from_spec 的代码,它不会加载模块。它创建它并将强制性的东西放入其中。显然加载是下一步。你只是在这个过程中走得还不够远,无法看到你想要得到什么。你可以找出打电话给谁来做,或者你可以。如果你想让模块有内容,它必须被执行。
  • 同时,至少在我自己的环境中的加载器上,缺少我接下来要调用的 exec_module。我不确定那是关于什么的。我认为这可能是一个版本的事情。我正在运行 3.6,因此它可能与长视图文档似乎暗示的方式不同。但很明显,如果您编写自己的加载程序,您可以制作正确的内容......
【解决方案2】:

问题在于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 的属性。这就是它返回 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 的属性。这就是它返回 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语句的工作方式不同。

【讨论】:

  • 感谢您详细说明行为是什么。我意识到我不清楚我想重现import 的行为,因为那是我正在尝试的模型。我不是在寻找对我不想要的行为而不是我想要的行为的更详细的解释,尽管这可能有助于弄清楚如何纠正。我已更改问题标题以使其更清楚。但是,如果您阅读了文本和我对您的评论的回复,则应该更清楚这就是意图。
猜你喜欢
  • 1970-01-01
  • 2018-09-17
  • 1970-01-01
  • 2016-07-22
  • 1970-01-01
  • 2021-05-26
  • 2021-08-31
  • 1970-01-01
  • 2020-05-22
相关资源
最近更新 更多