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

Python import search path: what happens first?

Python 文档中关于导入的两个部分的措辞似乎有些模棱两可。

来自"The Module Search Path"

When a module named spam is imported, the interpreter first searches for a built-in module with that name. If not found, it then searches for a file named spam.py in a list of directories given by the variable sys.path.

来自 "The Module Cache":

The first place checked during import search is sys.modules. This mapping serves as a cache of all modules that have been previously imported, including the intermediate paths.

以下哪一项更准确地描述了 Python 的导入系统内部发生的情况?下面的逻辑会说它们不能共存,因为 sys.modules 很可能包含非内置模块,并且可能排除一些内置模块。


我的困惑源于此:

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

如果我启动一个新的解释器会话,sys.modules 包含 大多数 内置函数,但从 sys.builtin_module_names 中排除一些东西:即 gctime,等等。此外,您可以导入 3rd 方包,这些包将被放入 sys.modules,此时 sys.modules 不再是仅包含内置模块的字典。所以,所有这些似乎都在说,“sys.modules != 内置模块。”

当您导入 模块时,解释器首先搜索内置函数,然后sys.path。但这只有在您真正导入模块时才会如此。 导入模块之前,有一个缓存可以搜索。如果模块已经在缓存中,则不会再次导入。

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


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

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

A module can contain executable statements as well as function definitions. These statements are intended to initialize the module. They are executed only the first time the module name is encountered in an import statement.

...

Note For efficiency reasons, each module is only imported once per interpreter session.

它没有解释发生这种情况的机制,因为这只是一个教程。


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

请注意,如果模块已经被导入,Python 避免执行模块的语句,或者为了效率只导入一次,这并不完全正确。这是默认加载程序将模块放入 sys.modules 缓存这一事实的结果。而且,如果事后更换加载器或使用缓存进行修改,实际上一个模块将被导入并执行多次。

后续部分——从下一节开始,Finders and loaders——类似地描述了如何找到模块的细节,比教程的模块搜索路径部分更严格、更详细:

Python includes a number of default finders and importers. The first one knows how to locate built-in modules, and the second knows how to locate frozen modules. A third default finder searches an import path for modules.

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


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

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

(在 IPython 下,或者如果您导入了诸如 six 之类的有助于重命名模块的内容,或者如果您导入了诸如 requests 之类的内容以嵌入版本化模块,您会有几个额外的发现者。)

BuiltinImporter is documented in the importlib library docs. (If you're wondering why it's not called BuiltinFinder, a finder that's also its own loader is called an importer.) What it actually does is look at sys.builtin_module_names 并调用特定于实现的函数来处理在那里找到的任何内容。


In CPython 3.6 (apologies for jumping back and forth between 3.6 and 3.7, but it shouldn't matter here…), the implementation-specific function it calls is _imp.create_builtin,你可以从那里追踪一些东西。

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

所以 create_builtin 函数(或者,对于不同的实现,无论它用来实现 BuiltinImporter 的什么)必须能够导入预安装的 so/dll/pyd/dylib 模块Python.

你需要区分sys.pathsys.modules

sys.modules This is a dictionary that maps module names to modules which have already been loaded. This can be manipulated to force reloading of modules and other tricks. Note that removing a module from this dictionary is not the same as calling reload() on the corresponding module object.

当我在 jupyter notebook 中加载 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 A list of strings that specifies the search path for modules. Initialized from the environment variable PYTHONPATH, plus an installation-dependent default.

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

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