Python导入搜索路径:首先发生什么?

时间:2018-06-25 19:10:01

标签: python python-3.x python-import

Python文档中有关导入的两部分似乎有些含糊。

来自"The Module Search Path"

  

导入名为spam的模块时,解释器首先搜索具有该名称的内置模块。如果找不到,它将在变量spam.py给出的目录列表中搜索名为sys.path的文件。

来自"The Module Cache"

  

在导入搜索期间检查的第一位是sys.modules。此映射充当所有先前导入的模块(包括中间路径)的缓存。

其中哪些可以更准确地表示Python导入系统内部发生的情况?下面的逻辑将说明它们不能共存,因为sys.modules可以很好地包含不是内置的模块,并且可以排除某些内置的模块。


这是我困惑的地方:

sys.modules用于缓存已导入的模块;它不是明确地用于存储内置模块的完整列表。 (我认为最接近的是sys.built_in_modules,但这还不包括具有.__file__属性的内容,例如math。)

如果我启动新的解释器会话,则sys.modules包含 most 个内置函数,但不包括sys.builtin_module_names中的某些内容:gc和{{1 }}等。另外,您可以导入第3方软件包,将其放入time中,此时sys.modules当然不再是仅包含内置模块的字典。因此,所有这些似乎都说:“ sys.modules!=内置模块。”

3 个答案:

答案 0 :(得分:1)

导入模块时,解释器首先搜索内置模块,然后搜索sys.path。但这仅在您确实要导入模块的情况下。 之前,导入模块需要搜索缓存。如果模块已经在缓存中,则不会再次导入。

答案 1 :(得分:1)

您正在查看两种完全不同的信息源,即教程和语言参考。


教程部分The Module Search Path(仅描述默认行为)也仅描述了实际导入模块时发生的情况。

如果模块已经在缓存中,则不会发生此过程。这里没有解释,因为上一节More on Modules中已经介绍了它:

  

一个模块可以包含可执行语句以及函数定义。这些语句旨在初始化模块。只有在import语句中第一次遇到模块名称时,它们才会执行。

     

...

     

注意出于效率原因,每个模块在每个解释器会话中仅导入一次。

由于这只是一个教程,因此没有说明发生这种情况的机制。


同时,在导入系统的参考文档中,module cache部分说明了import语句中发生的第一件事。

请注意,如果已导入模块,Python会避免执行模块的语句,或者为提高效率而仅导入一次,这并非完全正确。这是由于默认的加载程序将模块放入sys.modules缓存中的结果。而且,如果事后替换装载程序或猴子用缓存,则实际上模块将被多次导入并执行。

后续部分(从下一节Finders and loaders开始)比本教程的“模块搜索路径”部分更严格,更详细地描述了如何找到模块的详细信息:

  

Python包含许多默认查找器和导入器。第一个知道如何找到内置模块,第二个知道如何找到冻结的模块。第三个默认查找器在导入路径中搜索模块。

同样,解释器首先搜索内置模块并非完全正确。相反,解释器仅按顺序搜索其查找器,默认情况下,第一个查找器是内置的模块查找器。但是,如果您更改查找器列表,Python将不会首先搜索内置函数。


实际上,如果在默认安装的CPython 3.7上打印出sys.meta_path,您将看到:

<class '_frozen_importlib.BuiltinImporter'>
<class '_frozen_importlib.FrozenImporter'>
<class '_frozen_importlib_external.PathFinder'>

(在IPython中,或者如果您导入了有助于重命名模块的six之类的东西,或者如果您导入了嵌入版本控制的模块之类的requests之类的东西,额外的发现者。)

importlib库文档中记录了BuiltinImporter。 (如果您想知道为什么它不被称为BuiltinFinder,那么它本身也是一个加载程序的查找程序被称为导入程序。)它的实际作用是查看sys.builtin_module_names并调用特定于实现的函数来处理那里找到任何东西。


In CPython 3.6(在3.6和3.7之间来回跳转的道歉,但在这里不重要…),它调用的特定于实现的功能是_imp.create_builtin,您可以从中跟踪内容在那里。

但是要注意的关键是,并不是builtin_module_names中的所有内容实际上都是“内置”的,因为它是预先导入的。例如,在常规安装中,您可能会在此处看到_ast,但看不到sys.modules['_ast']

因此,create_builtin函数(或者,对于其他实现,无论其用于实现BuiltinImporter的功能)必须能够导入预先安装的so / dll / pyd / dylib模块使用Python。

答案 2 :(得分:0)

您需要区分sys.pathsys.modules

  

sys.modules 这是将模块名称映射到模块的字典   已经加载的可以操纵以强制   重新加载模块和其他技巧。请注意,卸下模块   从此字典中获取的内容与在上调用reload()不同   相应的模块对象。

当我在jupyter笔记本中加载sys.path时,显示映射到文件位置的已加载模块名称的字典-

{'IPython': <module 'IPython' from 'C:\\Users\\User\\Anaconda3\\lib\\site-packages\\IPython\\__init__.py'>,
 'IPython.core': <module 'IPython.core' from 'C:\\Users\\User\\Anaconda3\\lib\\site-packages\\IPython\\core\\__init__.py'>,.....}

这是我的模块缓存,但是当我尝试

sys.modules['numpy']

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-6-44b02d746fe5> in <module>()
----> 1 sys.modules['numpy']

KeyError: 'numpy'

因为numpy不在我的模块缓存中。我将要求python在sys.path中定义的一组固定目录中查找它。我认为合适的字符串列表,可以在其中添加或删除路径。

  

sys.path (字符串列表),用于指定模块的搜索路径。   从环境变量PYTHONPATH初始化,再加上   取决于安装的默认值。

如果python在我的sys.path集中找到了该库;它将在我的sys.modules中为其创建一个映射,以便在活动环境中进行快速访问。

import numpy
sys.modules['numpy']
#<module 'numpy' from 'C:\\Users\\User\\Anaconda3\\lib\\site-packages\\numpy\\__init__.py'>