当你导入一个包时会发生什么?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (14)

为了提高效率,我试图弄清楚python如何处理对象堆(以及命名空间系统,但它或多或少都很清晰)。所以,基本上,我试图了解何时将对象加载到堆中,其中有多少个存在,它们的存活时间等等。

而我的问题是,当我使用一个软件包并从中导入时

from pypackage import pymodule

什么对象被加载到内存中(进入python解释器的对象堆)?更普遍的是:会发生什么?:)

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

我发现了以下关于模块导入的相同问题:

当Python导入模块时,它首先检查模块注册表(sys.modules)以查看模块是否已经导入。如果是这样的话,Python会按原样使用现有的模块对象。 否则,Python会做这样的事情:

  • 创建一个新的空模块对象(这本质上是一个字典)
  • 将该模块对象插入到sys.modules字典中
  • 加载模块代码对象(如有必要,先编译模块)
  • 在新模块的命名空间中执行模块代码对象。代码分配的所有变量将通过模块对象提供。

对于有关包装的相同解释,我们将不胜感激。

顺便说一下,使用包的模块名称会被sys.modules奇怪地添加进来:

>>> import sys
>>> from pypacket import pymodule
>>> "pymodule" in sys.modules.keys()
False
>>> "pypacket" in sys.modules.keys()
True

同样的问题也存在一个实际问题。

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

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

提问于
用户回答回答于

关于导入包

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

所以是的,包的子模块不会被加载,除非__init__.py明确地这样做。如果你这样做from package import module,只会module被加载,除非它从包中导入其他模块。

sys.modules 从软件包加载的模块名称

从包中导入模块时,添加的名称是sys.modules“限定名称”,用于指定模块名称以及导入它的任何包的点分隔名称。所以如果你这样做from package.subpackage import mod,添加的sys.modules是什么"package.subpackage.mod"

仅导入模块的一部分

通常不需要导入整个模块而不是一个函数。你说这是“痛苦的”,但实际上它几乎从来没有。

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

如果你的模块在模块导入时有昂贵的操作(例如,它们是全局模块级代码而不是函数内部的),但对于模块中所有功能的使用并不重要,如果你愿意,重新设计你的模块以延迟加载,直到以后。也就是说,如果你的模块做了如下的事情:

def simpleFunction():
    pass

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

你可以改变它

def simpleFunction():
    pass

def loadData():
    # open files, read huge amounts of data, do slow stuff here

然后告诉人们“ someModule.loadData()在你想要加载数据时打电话”。或者,如你所建议的那样,您可以将模块的昂贵部件放入包装内的独立模块中。

我从来没有发现导入模块会导致有意义的性能影响,除非模块已经足够大,可以合理地分解成更小的模块。制作大量的小模块,每个小模块都包含一个函数,除了维护令人头疼的事情之外,不需要跟踪所有这些文件。你真的有一个特定的情况,这对你有所帮助吗?

此外,关于你的最后一点,据我所知,与纯Python模块相同,适用于C扩展模块的全有或全无负载策略。很显然,就像使用Python模块一样,你可以将事物分解为更小的扩展模块,但是如果不from someExtensionModule import someFunction运行其他代码作为扩展模块的一部分进行打包,你也无法做到这一点。

用户回答回答于

导入模块时出现的步骤大致顺序如下:

  1. Python试图找到这个模块,sys.modules如果找到它就不会做任何事情。软件包由全名键控,所以虽然pymodule缺少sys.modules,但pypacket.pymodule会在那里(并可以作为sys.modules["pypacket.pymodule"]
  2. Python找到实现该模块的文件。如果模块是程序包的一部分(由x.y语法决定),它将查找名称x同时包含一个__init__.pyy.py(或另外的子包)的目录。位于最底部的文件将是.py文件,.pyc文件或.so/ .pyd文件。如果找不到适合该模块的文件,ImportError则会提出。
  3. 一个空的模块对象被创建,并且该模块中的代码执行与所述模块的__dict__作为执行命名空间。1
  4. 模块对象被放入sys.modules并注入到导入器的名称空间中。

第3步是“对象被加载到内存中”的位置:所讨论的对象是模块对象,以及其中包含的名称空间的内容__dict__。此字典通常包含作为执行所有的副作用创建顶层函数和类defclass通常包含在每个模块中,和其他顶层语句。

请注意,以上仅描述了默认实现import。有多种方式可以通过覆盖定制进口的行为,例如__import__内置或实施进口挂钩

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

扫码关注云+社区