导入包时会发生什么情况?

2024-06-06 13:04:48 发布

您现在位置:Python中文网/ 问答频道 /正文

为了提高效率,我试图弄清楚python如何处理它的对象堆(以及名称空间系统,但它或多或少是清楚的)。所以,基本上,我试图了解对象何时被加载到堆中,有多少对象,它们的生存时间等等

我的问题是当我处理一个包并从中导入一些东西时

from pypackage import pymodule

哪些对象被加载到内存中(在python解释器的对象堆中)?更笼统地说:会发生什么?:)

我想上面的例子是这样的: 包pypackage的某些对象在内存中创建(它包含有关包的一些信息,但不太多),模块pymodule被加载到内存中,其引用在本地名称空间中创建。这里重要的是:没有在内存中创建pypackage的其他模块(或其他对象),除非它被明确地声明(在模块本身中,或者在包初始化技巧和钩子中的某个地方,我不熟悉)。最后,内存中唯一大的东西是pymodule(即导入模块时创建的所有对象)。是这样吗?如果有人能澄清一下这件事,我将不胜感激。也许你能给我一些有用的文章吗?(文档包括更具体的内容)

关于模块导入的同一问题,我发现如下:

When Python imports a module, it first checks the module registry (sys.modules) to see if the module is already imported. If that’s the case, Python uses the existing module object as is.

Otherwise, Python does something like this:

  • Create a new, empty module object (this is essentially a dictionary)
  • Insert that module object in the sys.modules dictionary
  • Load the module code object (if necessary, compile the module first)
  • Execute the module code object in the new module’s namespace. All variables assigned by the code will be available via the module object.

如果你能对包裹做出同样的解释,我将不胜感激。在

顺便说一句,对于包,一个模块名会奇怪地添加到sys.modules中:

^{pr2}$

同样的,还有一个实际问题。在

当我构建一组工具时,这些工具可能会在不同的进程和程序中使用。我把它们放在模块里。我别无选择,只能加载一个完整的模块,即使我只想使用其中声明的一个函数。正如我所见,通过制作小模块并将它们放入一个包中(如果一个包在只导入其中一个模块时没有加载其所有模块),可以使这个问题变得不那么痛苦。在

在Python中有没有更好的方法来创建这样的库?(仅仅是函数,它们在模块中没有任何依赖关系)C扩展是否可能?在

问了这么长的问题。在


Tags: 模块the对象内存名称modules声明object
2条回答

你有几个不同的问题。在

关于导入软件包

导入包时,步骤顺序与导入模块时相同。唯一的区别是包的代码(即创建“模块代码对象”的代码)是包的__init__.py的代码。在

所以是的,除非__init__.py显式加载包的子模块。如果执行from package import module,则只加载{},除非它从包中导入其他模块。在

sys.modules从包

加载的模块的名称

从包导入模块时,^{中添加的名称是}是“限定名”,它指定模块名以及从中导入的任何包的以点分隔的名称。所以如果你做from package.subpackage import mod,那么添加到sys.modules的是{}。在

仅导入模块的一部分

通常情况下,不必只导入一个函数,而是导入整个模块。你说这是“痛苦的”,但实际上几乎从来没有。在

如您所说,如果函数没有外部依赖关系,那么它们只是纯Python,加载它们不会花费太多时间。通常,如果导入一个模块需要很长时间,那是因为它加载其他模块,这意味着它确实有外部依赖性,你必须加载整个模块。在

如果您的模块具有在模块导入时发生的昂贵操作(即,它们是全局模块级代码,而不是函数内部的操作),但对于使用模块中的所有函数不是必需的,那么您可以(如果愿意)重新设计模块以将加载推迟到以后。也就是说,如果您的模块执行以下操作:

def simpleFunction():
    pass

# open files, read huge amounts of data, do slow stuff here

你可以改成

^{pr2}$

然后告诉人们“当你想加载数据时打电话给someModule.loadData()”。或者,正如您所建议的,您可以将模块中昂贵的部分放在一个包中各自独立的模块中。在

我从来没有发现导入模块会对性能产生有意义的影响,除非模块已经足够大,可以合理地将其分解为更小的模块。制作大量的每个包含一个功能的小模块不太可能给您带来任何好处,除了需要跟踪所有这些文件带来的维护麻烦之外。你真的有一个具体的情况,这对你有影响吗?在

另外,关于您的最后一点,据我所知,对于纯Python模块,同样的全有或全无加载策略适用于C扩展模块。显然,就像Python模块一样,您可以将内容拆分成更小的扩展模块,但是如果不运行作为扩展模块一部分打包的其余代码,您就不能from someExtensionModule import someFunction。在

导入模块时发生的步骤大致顺序如下:

  1. Python尝试在sys.modules中定位模块,如果找到它,则不执行任何其他操作。包是由它们的全名键控的,所以当sys.modules中缺少pymodule时,pypacket.pymodule将在那里(并且可以作为sys.modules["pypacket.pymodule"]获得)。

  2. Python定位实现模块的文件。如果模块是包的一部分,由x.y语法决定,它将查找名为x的目录,该目录同时包含__init__.py和{}(或其他子包)。最底层的文件将是.py文件、.pyc文件或.so/.pyd文件。如果没有找到适合该模块的文件,将引发ImportError

  3. 创建一个空的模块对象,模块中的代码是executed,模块的__dict__作为执行命名空间。1

  4. 模块对象放在sys.modules中,并注入导入器的命名空间中。

步骤3是“对象加载到内存”的点:有问题的对象是模块对象,以及其__dict__中包含的命名空间的内容。这个dict通常包含顶级函数和类,这些函数和类是在执行所有defclass以及其他通常包含在每个模块中的顶级语句的副作用而创建的。在

注意,上面只描述了import的默认实现。有许多方法可以自定义导入行为,例如重写the ^{} built-in或实现import hooks。在


1如果模块文件是.py源文件,它将首先被编译到内存中,并执行编译产生的代码对象。如果是.pyc,则代码对象将由deserializing the file contents获得。如果模块是.so.pyd共享库,则将使用操作系统的共享库加载工具加载它,并调用init<module>C函数来初始化该模块。在

相关问题 更多 >