【问题标题】:What happens when you import a package?导入包时会发生什么?
【发布时间】:2013-02-23 04:20:09
【问题描述】:

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

【问题讨论】:

    标签: python memory-management heap-memory


    【解决方案1】:

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

    关于导入包

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

    【讨论】:

    • 谢谢!很好的答案。我现在没有很多功能的具体例子,只是问未来。是的,1function-1module 将是一场灾难)合理大小的模块应该很好用。
    【解决方案2】:

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

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

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

    3. 创建了一个空的模块对象,模块中的代码为executed,执行命名空间为模块的__dict__1

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

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

    请注意,以上仅描述了import 的默认实现。可以通过多种方式自定义导入行为,例如通过覆盖 the __import__ built-in 或实现 import hooks


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

    【讨论】:

    • 这个问题是关于 packages 而不是独立模块。您基本上只是重申了提问者已经说过他知道的内容。
    • @BrenBarn OP 提出了几个问题,我们选择回答不同的问题。既然他特别问,“我试图了解对象何时加载到堆中,其中有多少,它们存在多长时间等等。[...]哪些对象被加载到内存中(加载到python解释器)?”我不相信他知道关于模块的所有这些事情,因为如果他知道,他也会知道所有这些都适用于包。我的答案中的#2 包含特定于导入包的信息,以及#1 对明确问题的答案为什么pymodule不在sys.modules 中。
    • 非常感谢。尽管问题是关于包的,但模块的清晰性只会有好处。此外,由于这些信息现在在 StackOverflow 上,因此对于人们来说更易于使用谷歌搜索。他们不需要像我们中的一些人那样从所有互联网上收集信息来获得这个简单的结构。而且,在实践中,当您在文档、博客、问答等中搜索答案时,您可能了解模块,但不知道包的工作方式相同;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-26
    • 2014-04-17
    • 1970-01-01
    • 1970-01-01
    • 2020-03-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多