19

为了效率起见,我试图弄清楚 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抱歉这么长的问题。

4

2 回答 2

12

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

关于导入包

导入包时,步骤顺序与导入模块时相同。唯一的区别是包的代码(即创建“模块代码对象”的代码)是包的__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运行打包为该扩展模块的一部分的其余代码。

于 2013-03-07T19:47:16.553 回答
2

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

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

  2. Python 定位实现模块的文件。如果模块是包的一部分,由x.y语法决定,它将查找x包含 an__init__.pyy.py(或更多子包)的目录。位于最底部的文件将是.py文件、.pyc文件或.so/.pyd文件。如果找不到适合该模块的文件,ImportError则会引发 an。

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

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

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

请注意,以上仅描述了import. 有多种方法可以自定义导入行为,例如通过覆盖内置__import__实现导入钩子


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

于 2013-03-07T19:42:01.930 回答