您正在查看两个完全不同的信息来源,教程和语言参考。
教程部分模块搜索路径(除了描述默认行为之外)还仅描述了当模块实际导入时发生的情况。
如果模块已经在缓存中,此过程不会发生。这里没有解释,因为它在前一节更多关于模块中已经涵盖:
模块可以包含可执行语句和函数定义。这些语句旨在初始化模块。它们只在第一次遇到模块名称时执行导入语句。
...
注意:出于效率考虑,每个模块在解释器会话中仅导入一次。
它没有解释这是如何发生的机制,因为这只是一个教程。
同时,在导入系统的参考文档中,
模块缓存部分解释了
import
语句执行的第一件事情。
请注意,并不完全正确的是Python会避免执行已经被导入的模块语句,或者仅为提高效率而导入一次。这是因为默认加载器将模块放入
sys.modules
缓存中的结果。如果您替换了加载器,或在事后对缓存进行修改,则一个模块实际上会被导入和执行多次。
随后的各个部分——从下一部分
查找器和加载器开始——以更严格和详细的方式描述了如何找到模块,比教程中的“模块搜索路径”部分更加详细:
Python包括许多默认的查找器和导入器。第一个知道如何定位内置模块,第二个知道如何定位冻结模块。第三个默认查找器在导入路径中搜索模块。
所以,再次强调,解释器不是首先搜索内置模块。相反,解释器只是按顺序搜索其查找器,默认情况下,第一个查找器是内置模块查找器。但如果您更改查找器列表,则Python将不会首先搜索内置模块。
事实上,如果您在默认安装的CPython 3.7上打印
sys.meta_path
,您将看到:
<class '_frozen_importlib.BuiltinImporter'>
<class '_frozen_importlib.FrozenImporter'>
<class '_frozen_importlib_external.PathFinder'>
(在IPython下,或者如果您导入了像
six
这样帮助重命名模块的东西,或者如果您导入了像
requests
这样嵌入版本化模块的内容,您将拥有一些额外的查找器。)
那个
BuiltinImporter
在
importlib
库文档中有记录。(如果您想知道为什么它不叫做
BuiltinFinder
,一个既是其自己的查找器又是其自己的加载器的查找器被称为导入器。) 它实际上所做的是查看
sys.builtin_module_names
并调用一个特定于实现的函数来处理其中找到的任何内容。
在CPython 3.6(抱歉来回跳转3.6和3.7,但这里不应该有影响...)中,它调用的是特定于实现的函数{{link2:_imp.create_builtin
}},您可以从那里追踪事物。
但需要注意的关键是,并非builtin_module_names
中的所有内容都是预先导入的意义上的“内置”。例如,通过正常安装,您可能会看到_ast
,但没有sys.modules['_ast']
。
因此,create_builtin
函数(或对于不同的实现,用于实现BuiltinImporter
的任何内容)必须能够导入随Python预安装的so/dll/pyd/dylib模块。
sys.modules != built in modules
是令人困惑的地方。但是缓存和搜索路径是导入系统的两个不同组件。它们都有各自的作用,那么为什么一个比另一个更准确呢?这些组件有不同的角色。 - pyeR_bizsys.modules
!= 内置模块。这一点是显而易见的。我的困惑在于这两个摘录都明确表示,“搜索的第一个位置是…”或“搜索的第一件事是…”。这两个陈述如何同时正确,因为它们似乎相互冲突? - Brad Solomon