在Python中,有两种默认的元路径查找器:基于路径的查找器(PathFinder)和源文件查找器(SourceFileFinder)。基于路径的查找器会搜索包含一个路径条目列表的import path,每个路径条目指定一个用于搜索模块的位置。
然而,基于路径的查找器本身并不知道如何进行导入。它只是遍历单独的路径条目,将它们各自关联到某个知道如何处理特定类型路径的路径条目查找器。
默认的路径条目查找器集合实现了在文件系统中查找模块的所有语义,可以处理多种特殊文件类型,如Python源码(.py文件)、Python字节码(.pyc文件)和共享库(如.so文件)。在标准库中,zipimport模块的支持使得默认路径条目查找器还能处理来自zip文件的所有上述文件类型。
路径条目不必仅限于文件系统位置,它们可以指向URL、数据库查询或任何其他可以用字符串指定的位置。基于路径的查找器还提供了额外的钩子和协议以便能扩展和定制可搜索路径条目的类型。例如,如果你想要支持网络URL形式的路径条目,你可以编写一个实现HTTP语义在网络上查找模块的钩子。这个钩子(可调用对象)应当返回一个支持特定协议的路径条目查找器,以被用来获取一个专门针对来自网络的模块的加载器。
注意:本篇教程使用了查找器这一术语,并通过 meta path finder 和 path entry finder 两个术语来明确区分它们。 这两种类型的查找器非常相似,支持相似的协议,且在导入过程中以相似的方式运作,但关键的一点是要记住它们是有微妙差异的。 元路径查找器作用于导入过程的开始,主要是启动 sys.meta_path 遍历。
相比之下,路径条目查找器在某种意义上说是基于路径的查找器的实现细节,实际上,如果需要从 sys.meta_path 移除基于路径的查找器,并不会有任何路径条目查找器被发起调用。
一、路径条目查找器
path based finder会负责查找和加载通过 path entry 字符串来指定位置的 Python 模块和包。 多数路径条目所指定的是文件系统中的位置,但它们并不必受限于此。作为一种元路径查找器,基于路径的查找器实现了上文描述的find_spec()协议,但是它还对外公开了一些附加钩子,可被用来定制模块如何从import path查找和加载。
有三个变量由基于路径的查找器、sys.path、sys.path_hooks和sys.path_importer_cache所使用。包对象的__path__属性也会被使用。它们提供了可用于定制导入机制的额外方式。
sys.path 包含一个提供模块和包搜索位置的字符串列表, 它初始化自 PYTHONPATH 环境变量以及多种其他特定安装和实现专属的默认位置。 sys.path 中的条目可指定文件系统中的目录、zip 文件及其他可用于搜索模块的潜在 “位置” ,例如 URL 或数据库查询等。 在 sys.path 中应当只有字符串;所有其他类数据类型都会被忽略。
path based finder 是一种 meta path finder,因此导入机制会通过调用上文描述的基于路径的查找器的 find_spec() 方法来启动 import path 搜索。 当要向 find_spec() 传入 path 参数时,它将是一个可遍历的字符串列表 —— 通常为用来在其内部进行导入的包的 __path__ 属性。 如果 path 参数为 None,这表示最高层级的导入,将会使用 sys.path。
如果 sys.path_hooks 迭代结束时没有返回 path entry finder,则基于路径的如果 sys.path_hooks 迭代结束时没有返回 path entry finder,则基于路径的查找器 find_spec() 方法将在 sys.path_importer_cache 中存入 None(表示此路径条目没有对应的查找器),并返回 None,表示此 meta path finder 无法找到该模块。
如果 sys.path_hooks 中的某个 path entry hook 可调用对象的返回值是一个 path entry finder,则以下协议会被用来向查找器请求一个模块的规格说明,并在加载该模块时被使用。
当前工作目录的处理方式与 sys.path 中的其他条目略有不同。首先,如果发现当前工作目录不存在,则 sys.path_importer_cache 中不会存放任何值。其次,每个模块查找会对当前工作目录的值进行全新查找。第三,由 sys.path_importer_cache 所使用并由 importlib.machinery.PathFinder.find_spec() 所返回的路径将是实际的当前工作目录而非空字符串
二、路径条目查找器协议
为了支持模块和已初始化包的导入,也为了给命名空间包提供组成部分,路径条目查找器必须实现 find_spec() 方法。find_spec() 接受两个参数,即要导入模块的完整限定名称,以及(可选的)目标模块。 find_spec() 返回模块的完全填充好的规格说明。 这个规格说明总是包含“加载器”集合(但有一个例外)。
为了向导入机制提示该规格说明代表一个命名空间 portion,路径条目查找器会将 submodule_search_locations 设为一个包含该部分的列表。
较旧的路径条目查找器可能会实现这两个已弃用的方法中的一个而没有实现 find_spec()。 为保持向后兼容,这两个方法仍会被接受。 但是,如果在路径条目查找器上实现了 find_spec(),这两个遗留方法就会被忽略。
find_loader() 接受一个参数,即要导入模块的完整限定名称。 find_loader() 返回一个 2 元组,其中第一项是加载器而第二项是命名空间 portion。为了向后兼容其他导入协议的实现,许多路径条目查找器也同样支持元路径查找器所支持的传统 find_module() 方法。 但是路径条目查找器 find_module() 方法的调用绝不会带有 path 参数(它们被期望记录来自对路径钩子初始调用的恰当路径信息)。
路径条目查找器的 find_module() 方法已被弃用,因为它不允许路径条目查找器为命名空间包提供部分。如果 find_loader() 和 find_module() 同时存在于一个路径条目查找器中,导入系统将总是调用 find_loader() 而不选择 find_module()。