Python入门基础之import机制
本文深入解析Python的import机制,涵盖模块搜索、sys.modules缓存查找、元路径查找器(MetaPathFinder)以及加载器(Loader)的工作流程。通过解析find_spec、create_module和exec_module等核心方法,帮助开发者理解模块加载的底层原理及自定义导入钩子的实现方式。
一、前言
本文基于开源项目:
github.com/pwwang/pyth…
补充扩展讲解,希望能够让读者一文搞懂 Python 的 import 机制。
1.1 什么是 import 机制?
在编写 Python 代码时,若需调用其他模块中的代码,必须依赖 Python 的 import 机制。虽然 import 语句是最常见的触发方式,但它并非唯一途径。
此外,importlib.import_module 和 __import__ 函数同样可以用于引入其他模块的代码。
1.2 import 是如何执行的?
执行 import 语句时,Python 会依次完成两个关键步骤:
- 搜索目标模块
- 将模块名作为变量绑定至当前局部命名空间
模块搜索的实际执行由 __import__ 函数负责,其返回值随后被绑定到局部变量中。下文将详细解析 __import__ 函数的具体运作逻辑。
二、import 机制概览
下图展示了 import 机制的整体流程。当 import 机制被触发时,Python 首先检查 sys.modules 缓存中是否已存在该模块。若存在,则直接复用;若不存在,则继续后续流程。sys.modules 可视为一个缓存容器。值得注意的是,若 sys.modules 中对应模块的值为 None,系统将抛出 ModuleNotFoundError 异常。以下是一个简单的实验示例:
In [1]: import sys
In [2]: sys.modules['os'] = None
In [3]: import os
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-3-543d7f3a58ae> in <module>
----> 1 import os
ModuleNotFoundError: import of os halted; None in sys.modules
若在 sys.modules 中找到对应模块,且该导入由 import 语句触发,系统将把对应变量绑定至局部命名空间。
若缓存中未找到任何模块,系统将启动全新的导入流程。此时,Python 会遍历 sys.meta_path 列表,寻找符合条件的元路径查找器(meta path finder)。sys.meta_path 是一个存储元路径查找器的列表,默认包含以下三个查找器:
- 内置模块查找器
- 冻结模块(frozen module)查找器
- 基于路径的模块查找器。
In [1]: import sys
In [2]: sys.meta_path
Out[2]:
[_frozen_importlib.BuiltinImporter,
_frozen_importlib.FrozenImporter,
_frozen_importlib_external.PathFinder]
查找器的 find_spec 方法决定其是否能处理目标模块,并返回一个 ModuleSpec 对象。该对象包含加载模块所需的信息。若未返回合适的 ModuleSpec 对象,系统将检查 sys.meta_path 中的下一个元路径查找器。若遍历完所有查找器仍未找到合适的,系统将抛出 ModuleNotFoundError。引入不存在的模块时,因所有查找器均无法处理,便会发生此异常:
In [1]: import nosuchmodule
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-1-40c387f4d718> in <module>
----> 1 import nosuchmodule
ModuleNotFoundError: No module named 'nosuchmodule'
然而,若手动添加一个能处理该模块的查找器,则仍可成功导入:
In [1]: import sys
...:
...: from importlib.abc import MetaPathFinder
...: from importlib.machinery import ModuleSpec
...:
...: class NoSuchModuleFinder(MetaPathFinder):
...: def find_spec(self, fullname, path, target=None):
...: return ModuleSpec('nosuchmodule', None)
...:
...: # don't do this in your script
...: sys.meta_path = [NoSuchModuleFinder()]
...:
...: import nosuchmodule
---------------------------------------------------------------------------
ImportError Traceback (most recent call last)
<ipython-input-6-b7cbf7e60adc> in <module>
11 sys.meta_path = [NoSuchModuleFinder()]
12
---> 13 import nosuchmodule
ImportError: missing loader
当系统获知如何执行 find_spec 时,将不再抛出 ModuleNotFound 异常。但要成功加载模块,还需依赖加载器(loader)。
加载器是 ModuleSpec 对象的属性,负责决定模块的加载与执行方式。若将 ModuleSpec 对象比作“师父领进门”,那么加载器便是“修行在个人”。在加载器中,你可以完全自定义模块的加载与执行逻辑,甚至修改模块内容:
In [1]: import sys
...: from types import ModuleType
...: from importlib.machinery import ModuleSpec
...: from importlib.abc import MetaPathFinder, Loader
...:
...: class Module(ModuleType):
...: def __init__(self, name):
...: self.x = 1
...: self.name = name
...:
...: class ExampleLoader(Loader):
...: def create_module(self, spec):
...: return Module(spec.name)
...:
...: def exec_module(self, module):
...: module.y = 2
...:
...: class ExampleFinder(MetaPathFinder):
...: def find_spec(self, fullname, path, target=None):
...: return ModuleSpec('module', ExampleLoader())
...:
...: sys.meta_path = [ExampleFinder()]
In [2]: import module
In [3]: module
Out[3]: <module 'module' (<__main__.ExampleLoader object at 0x7f7f0d07f890>)>
In [4]: module.x
Out[4]: 1
In [5]: module.y
Out[5]: 2
从上述示例可见,加载器通常需实现 create_module 和 exec_module 两个重要方法。若实现了 exec_module 方法,则 create_module 也是必需的。若导入由 import 语句发起,create_module 返回的模块对象对应的变量将被绑定至当前局部变量。模块成功加载后,将被缓存至 sys.modules。再次加载该模块时,将直接引用 sys.modules 中的缓存。

三、import 勾子(import hooks)
为简化说明,前述流程图中未提及 import 机制的勾子(hooks)。实际上,你可以添加勾子来修改 sys.meta_path 或 sys.path,从而改变 import 机制的行为。在前面的例子中,我们直接修改了 sys.meta_path。实际上,你也可以通过勾子来实现:
In [1]: import sys
...: from types import ModuleType
...: from importlib.machinery import ModuleSpec
...: from importlib.abc import MetaPathFinder, Loader
...:
...: class Module(ModuleType):
...: def __init__(self, name):
...: self.x = 1
...: self.name = name
...:
...: class ExampleLoader(Loader):
...: def create_module(self, spec):
...: return Module(spec.name)
...:
...: def exec_module(self, module):
...: module.y = 2
...:
...: class ExampleFinder(MetaPathFinder):
...: def find_spec(self, fullname, path, target=None):
...: return ModuleSpec('module', ExampleLoader())
...:
...: def example_hook(path):
...: # some conditions here
...: return ExampleFinder()
...:
...: sys.path_hooks = [example_hook]
...: # force to use the hook
...: sys.path_importer_cache.clear()
...:
...: import module
...: module
Out[1]: <module 'module' (<__main__.ExampleLoader object at 0x7fdb08f74b90>)>
四、元路径查找器(meta path finder)
元路径查找器的职责是确认模块是否存在。这些查找器存放在 sys.meta_path 中供 Python 遍历(当然,它们也可通过 import 勾子返回,参见前述示例)。每个查找器必须实现 find_spec 方法。若查找器知道如何处理目标模块,find_spec 将返回 ModuleSpec 对象(见下节);否则返回 None。 与之前提到的一样,sys.meta_path 包含三种查找器:
- 内置模块查找器
- 冻结模块查找器
- 基于路径的查找器
此处重点讨论基于路径的查找器(path based finder)。它用于搜索一系列 import 路径,每条路径用于查找可加载的对应模块。默认的路径查找器实现了在文件系统特殊文件中查找模块的功能,这些特殊文件包括 Python 源文件(.py 文件)、Python 编译后代码文件(.pyc 文件)以及共享库文件(.so 文件)。若 Python 标准库中包含 zipimport,则相关压缩包文件也可用于查找可导入的模块。
路径查找器不仅限于文件系统内的文件,还可以查询 URL、数据库或其他任何可用字符串表示的地址。
你可以利用上节提供的勾子来实现对同类型地址模块的查找。例如,若想通过 URL 导入模块,可编写一个 import 勾子来解析该 URL 并返回路径查找器。
注意,路径查找器不同于元路径查找器。后者位于 sys.meta_path 中供 Python 遍历,而前者特指基于路径的查找器。
五、ModuleSpec 对象
每个元路径查找器必须实现 find_spec 方法。若该查找器知道如何处理目标模块,该方法将返回 ModuleSpec 对象。该对象有两个属性值得注意:模块名和查找器。若 ModuleSpec 对象的查找器为 None,系统将抛出类似 ImportError: missing loader 的异常。查找器将用于创建和执行模块(见下节)。
你可以通过 <module>.__spec__ 来查找模块的 ModuleSpec 对象:
In [1]: import sys
In [2]: sys.__spec__
Out[2]: ModuleSpec(name='sys', loader=<class '_frozen_importlib.BuiltinImporter'>)
六、加载器(loader)
加载器通过 create_module 创建模块,并通过 exec_module 执行模块。通常,若模块为 Python 模块(非内置或动态扩展),则需在模块的 __dict__ 空间上执行其代码。若模块代码执行失败,将抛出 ImportError 异常;其他执行过程中的异常也会被抛出。
在绝大多数情况下,查找器和加载器是同一对象。此时,查找器的 find_spec 方法返回的 ModuleSpec 对象的 loader 属性将指向其自身。
我们可以使用 create_module 动态创建模块;若其返回 None,Python 会自动创建一个模块。
七、总结
Python 的 import 机制灵活而强大。以上介绍主要基于官方文档及较新的 Python 3.6+ 版本。受篇幅限制,许多细节并未包含其中,例如子模块的加载、模块代码的缓存机制等。文章中难免出现疏漏,如有任何问题,欢迎到 github.com/pwwang/pyth… 开 issue 提问及讨论。
到此这篇关于Python入门基础之import机制的文章就介绍到这了,更多相关Python import机制内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













