初窥 Python 的 import 机制


初窥 Python 的 import 机制

文章插图
 
作者:pwwang
一、前言本文基于开源项目:
【初窥 Python 的 import 机制】https://github.com/pwwang/Python-import-system
补充扩展讲解 , 希望能够让读者一文搞懂 Python 的 import 机制 。
1.1 什么是 import 机制?通常来讲 , 在一段 Python 代码中去执行引用另一个模块中的代码 , 就需要使用 Python 的 import 机制 。import 语句是触发 import 机制最常用的手段 , 但并不是唯一手段 。
importlib.import_module 和 __import__ 函数也可以用来引入其他模块的代码 。
1.2 import 是如何执行的?import 语句会执行两步操作:
  1. 搜索需要引入的模块
  2. 将模块的名字作为变量绑定到局部变量中
搜索步骤实际上是通过 __import__ 函数完成的 , 而其返回值则会作为变量被绑定到局部变量中 。下面我们会详细聊到 __import__ 函数是如果运作的 。
二、import 机制概览下图是 import 机制的概览图 。不难看出 , 当 import 机制被触发时 , Python 首先会去 sys.modules 中查找该模块是否已经被引入过 , 如果该模块已经被引入了 , 就直接调用它 , 否则再进行下一步 。这里 sys.modules 可以看做是一个缓存容器 。值得注意的是 , 如果 sys.modules 中对应的值是 None 那么就会抛出一个 ModuleNotFoundError 异常 。下面是一个简单的实验:
In [1]: import sysIn [2]: sys.modules['os'] = NoneIn [3]: import os---------------------------------------------------------------------------ModuleNotFoundError                       Traceback (most recent call last)<ipython-input-3-543d7f3a58ae> in <module>----> 1 import osModuleNotFoundError: import of os halted; None in sys.modules如果在 sys.modules 找到了对应的 module , 并且这个 import 是由 import 语句触发的 , 那么下一步将对把对应的变量绑定到局部变量中 。
如果没有发现任何缓存 , 那么系统将进行一个全新的 import 过程 。在这个过程中 Python 将遍历 sys.meta_path 来寻找是否有符合条件的元路径查找器(meta path finder) 。sys.meta_path 是一个存放元路径查找器的列表 。它有三个默认的查找器:
  • 内置模块查找器
  • 冻结模块(frozen module)查找器
  • 基于路径的模块查找器 。
In [1]: import sysIn [2]: sys.meta_pathOut[2]: [_frozen_importlib.BuiltinImporter, _frozen_importlib.FrozenImporter, _frozen_importlib_external.PathFinder]查找器的 find_spec 方法决定了该查找器是否能处理要引入的模块并返回一个 ModeuleSpec 对象 , 这个对象包含了用来加载这个模块的相关信息 。如果没有合适的 ModuleSpec 对象返回 , 那么系统将查看 sys.meta_path 的下一个元路径查找器 。如果遍历 sys.meta_path 都没有找到合适的元路径查找器 , 将抛出 ModuleNotFoundError 。引入一个不存在的模块就会发生这种情况 , 因为 sys.meta_path 中所有的查找器都无法处理这种情况:
In [1]: import nosuchmodule---------------------------------------------------------------------------ModuleNotFoundError                       Traceback (most recent call last)<ipython-input-1-40c387f4d718> in <module>----> 1 import nosuchmoduleModuleNotFoundError: 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 nosuchmoduleImportError: missing loader


推荐阅读