为了效率,我试图找出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抱歉这么长的问题。
答案 0 :(得分:10)
这里有几个不同的问题。 。
导入包时,步骤顺序与导入模块时的步骤相同。唯一的区别是包的代码(即创建“模块代码对象”的代码)是包__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 :(得分:2)
导入模块时发生的大致步骤顺序如下:
Python尝试在sys.modules
中找到该模块,如果找到则不会执行任何其他操作。软件包的全名是关键字,因此pymodule
缺少sys.modules
,pypacket.pymodule
会在那里(并且可以sys.modules["pypacket.pymodule"]
获取。
Python找到实现模块的文件。如果模块是包的一部分,由x.y
语法确定,它将查找包含x
和__init__.py
(或更多子包)的名为y.py
的目录)。最底部的文件将是.py
文件,.pyc
文件或.so
/ .pyd
文件。如果找不到适合该模块的文件,则会引发ImportError
。
创建一个空模块对象,模块中的代码为executed,模块的__dict__
为执行命名空间。 1
模块对象放在sys.modules
中,并注入导入器的命名空间。
第3步是“对象加载到内存中”的点:所讨论的对象是模块对象,以及__dict__
中包含的命名空间的内容。此dict通常包含顶级函数和类,这些函数和类是执行所有def
,class
以及通常包含在每个模块中的其他顶级语句的副作用。
请注意,上述内容仅限于import
的默认实现。可以通过多种方式自定义导入行为,例如覆盖the __import__
built-in或实施import hooks。
1 如果模块文件是.py
源文件,它将首先编译到内存中,并且将执行编译产生的代码对象。如果是.pyc
,则代码对象将由deserializing the file contents获取。如果模块是.so
或.pyd
共享库,则将使用操作系统的共享库加载工具加载它,并将调用init<module>
C函数来初始化模块