繁体   English   中英

导入包时会发生什么?

[英]What happens when you import a package?

为了效率,我试图弄清楚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扩展?

PS抱歉这么长的问题。

你在这里有几个不同的问题。

关于导入包

导入包时,步骤顺序与导入模块时的步骤相同。 唯一的区别是包的代码(即创建“模块代码对象”的代码)是包的__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() ”。 或者,正如您所建议的那样,您可以将模块的昂贵部分放入包中的独立模块中。

我从来没有发现导入模块会导致有意义的性能影响,除非模块已经足够大,可以合理地分解为更小的模块。 制作大量包含一个功能的微小模块不可能获得除维护头痛之外的任何东西,因为必须跟踪所有这些文件。 你真的有一个特定的情况,这会对你产生影响吗?

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

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

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

  2. Python找到实现该模块的文件。 如果模块是包的一部分,由xy语法确定,它将查找名为x目录,其中包含__init__.pyy.py (或其他子包)。 最底部的文件将是.py文件, .pyc文件或.so / .pyd文件。 如果找不到适合该模块的文件,则会引发ImportError

  3. 一个空的模块对象被创建,并且该模块中的代码执行与所述模块的__dict__作为执行命名空间。 1

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

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

请注意,上面仅描述了import的默认实现。 可以通过多种方式自定义导入行为,例如通过覆盖__import__内置或通过实现导入挂钩


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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM