初窥 Python 的 import 机制( 二 )

可以看到 , 当我们告诉系统如何去 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 moduleIn [3]: moduleOut[3]: <module 'module' (<__main__.ExampleLoader object at 0x7f7f0d07f890>)>In [4]: module.xOut[4]: 1In [5]: module.yOut[5]: 2从上面的例子可以看到 , 一个加载器通常有两个重要的方法 create_module 和 exec_module 需要实现 。如果实现了 exec_module 方法 , 那么 create_module 则是必须的 。如果这个 import 机制是由 import 语句发起的 , 那么 create_module 方法返回的模块对象对应的变量将会被绑定到当前的局部变量中 。如果一个模块因此成功被加载了 , 那么它将被缓存到 sys.modules 。如果这个模块再次被加载 , 那么 sys.modules 的缓存将会被直接引用 。

初窥 Python 的 import 机制

文章插图
 
三、import 勾子(import hooks)为了简化 , 我们在上述的流程图中 , 并没有提到 import 机制的勾子 。实际上你可以添加一个勾子来改变 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   ...: moduleOut[1]: <module 'module' (<__main__.ExampleLoader object at 0x7fdb08f74b90>)>


推荐阅读