本文介绍用于引入包的 Python importlib 库。
简介 {#简介}
importlib 包具有三重目标。
-
在 Python 源代码中提供 import 语句的实现(并且因此而扩展 import() 函数)。 这提供了一个可移植到任何 Python 解释器的 import 实现。 与使用 Python 以外的编程语言实现的方式相比这一实现也更易于理解。
-
实现 import 的部分被公开在这个包中,使得用户更容易创建他们自己的自定义对象 (通常被称为 importer) 来参与到导入过程中。
-
这个包也包含了对外公开用于管理 Python 包的各个方面的附加功能的模块:
- importlib.metadata 代表对来自第三方发行版的元数据的访问。
- importlib.resources 提供了用于对来自 Python 包的非代码"资源"的访问的例程。
下面介绍 importlib 的函数
importlib.import_module(name, package=None) {#importlib-import-module-name-package-None}
导入一个模块。 参数 name 指定了以绝对或相对导入方式导入什么模块 (比如要么像这样 pkg.mod
或者这样 ..mod
)。 如果参数 name 使用相对导入的方式来指定,那么 package 参数必须设置为那个包名,这个包名作为解析这个包名的锚点 (比如 import_module('..mod', 'pkg.subpkg')
将会导入 pkg.mod
)。
import_module() 函数是一个对 importlib.import () 进行简化的包装器。 这意味着该函数的所有语义都来自于 importlib.import ()。 这两个函数之间最重要的不同点在于 import_module() 返回指定的包或模块 (例如 pkg.mod),而 import() 返回最高层级的包或模块 (例如 pkg)。
importlib.invalidate_caches() {#importlib-invalidate-caches}
使查找器存储在 sys.meta_path 中的内部缓存无效。如果一个查找器实现了 invalidate_caches(),那么它会被调用来执行那个无效过程。 如果创建/安装任何模块,同时正在运行的程序是为了保证所有的查找器知道新模块的存在,那么应该调用这个函数。
importlib.reload (module) {#importlib-reload-module}
重新加载之前导入的 module。 那个参数必须是一个模块对象,所以它之前必须已经成功导入了。 这在你已经使用外部编辑器编辑过了那个模块的源代码文件并且想在退出 Python 解释器之前试验这个新版本的模块的时候将很适用。 函数的返回值是那个模块对象(如果重新导入导致一个不同的对象放置在 sys.modules 中,那么那个模块对象是有可能会不同)。
当执行 reload() 的时候:
-
Python 模块的代码会被重新编译并且那个模块级的代码被重新执行,通过重新使用一开始加载那个模块的 loader,定义一个新的绑定在那个模块字典中的名称的对象集合。扩展模块的 init 函数不会被调用第二次。
-
与Python中的所有的其它对象一样,旧的对象只有在它们的引用计数为0之后才会被回收。
-
模块命名空间中的名称重新指向任何新的或更改后的对象。
-
其他旧对象的引用(例如那个模块的外部名称)不会被重新绑定到引用的新对象的,并且如果有需要,必须在出现的每个命名空间中进行更新。
importlib.abc {#importlib-abc}
源代码: Lib/importlib/abc.py
importlib.abc 模块包含了 import 使用到的所有核心抽象基类。在实现核心的 ABCs 中,核心抽象基类的一些子类也提供了帮助。
ABC 类的层次结构:
class importlib.abc.MetaPathFinder {#class-importlib-abc-MetaPathFinder}
一个代表 meta path finder 的抽象基类。
-
find_spec (fullname , path , target=None)
一个用于查找指定模块的 spec 的抽象方法。 如果这是最高层级的导入,path 将为 None。 在其他情况下,这将是对子包或模块的搜索而 path 将是来自上级包的 path 值。 如果找不到 spec,则返回 None。 在传入时,target 是一个被查找器用来对返回的 spec 进行更有依据的猜测的模块对象。 在实现具体 MetaPathFinders 的时候 importlib.util.spec_from_loader() 将会很有用处。
-
invalidate_caches()
当被调用的时候,一个可选的方法应该将查找器使用的任何内部缓存进行无效。将在 sys.meta_path 上的所有查找器的缓存进行无效的时候,这个函数被 importlib.invalidate_caches() 所使用。
class importlib.abc.PathEntryFinder {#class-importlib-abc-PathEntryFinder}
一个抽象基类,代表 path entry finder。虽然与 MetaPathFinder 有些相似之处,但 PathEntryFinder 仅用于 importlib.machinery.PathFinder 提供的基于路径的导入子系统中。
-
find_spec (fullname , target=None)
一个抽象方法,用于查找指定模块的 spec。搜索器将只在指定的 path entry 内搜索该模块。找不到则会返回 None。在实现具体的 PathEntryFinders 代码时,可能会用到 importlib.util.spec_from_loader() 。
-
invalidate_caches()
可选方法,调用后应让查找器用到的所有内部缓存失效。要让所有缓存的查找器的缓存无效时,可供 importlib.machinery.PathFinder.invalidate_caches() 调用。
class importlib.abc.Loader {#class-importlib-abc-Loader}
loader 的抽象基类。
想要支持资源读取的加载器应当实现 importlib.resources.abc.ResourceReader 所规定的 get_resource_reader() 方法。
-
create_module (spec)
当导入一个模块的时候,一个返回将要使用的那个模块对象的方法。这个方法可能返回
None
,这暗示着应该发生默认的模块创建语义。" -
exec_module (module)
当一个模块被导入或重新加载时在自己的命名空间中执行该模块的的抽象方法。 该模块在 exec_module() 被调用时应该已经被初始化了。 当此方法存在时,必须要定义 create_module()。
-
load_module (fullname)
用于加载模块的传统方法。 如果模块无法被导入,则会引发 ImportError,在其他情况下将返回被加载的模块。
如果请求的模块已存在于 sys.modules 中,则该模块应当被使用并重新加载。 在其他情况下加载器应当创建一个新模块并在任何加载操作开始之前将其插入到 sys.modules 中,以防止来自导入的无限递归。 如果加载器插入了一个模块并且加载失败,则必须用加载器将其从 sys.modules 中移除;在加载器开始执行之前已经存在于 sys.modules 中的模块应当保持原样。
class importlib.abc.InspectLoader {#class-importlib-abc-InspectLoader}
-
get_code (fullname)
返回一个模块的代码对象,或如果模块没有一个代码对象(例如,对于内置的模块来说,这会是这种情况),则为 None。 如果加载器不能找到请求的模块,则引发 ImportError 异常。
-
is_package (fullname)
可选方法,如果模块为包,则返回 True,否则返回 False。 如果 loader 找不到模块,则会触发 ImportError。
class importlib.abc.FileLoader (fullname , path) {#class-importlib-abc-FileLoader-fullname-path}
一个继承自 ResourceLoader 和 ExecutionLoader,提供 ResourceLoader.get_data() 和 ExecutionLoader.get_filename() 具体实现的抽象基类。
-
name
加载器可以处理的模块的名字。
-
path
模块的文件路径
-
load_module (fullname)
调用super的
load_module()
。
class importlib.abc.SourceLoader {#class-importlib-abc-SourceLoader}
一个用于实现源文件(和可选地字节码)加载的抽象基类。
-
path_stats (path)
返回一个包含关于指定路径的元数据的
dict
的可选的抽象方法。 支持的字典键有:'mtime'
(必选项): 一个表示源码修改时间的整数或浮点数;'size'
(可选项):源码的字节大小。
-
path_mtime (path)
返回指定文件路径修改时间的可选的抽象方法。
-
set_data (path , data)
往一个文件路径写入指定字节的的可选的抽象方法。任何中间不存在的目录不会被自动创建。
class importlib.abc.Traversable {#class-importlib-abc-Traversable}
一个具有 pathlib.Path 中方法的子集并适用于遍历目录和打开文件的对象。
-
name
抽象属性。 此对象的不带任何父引用的基本名称。
-
abstractmethod iterdir()
产出 self 中的 Traversable 对象。
-
abstractmethod is_dir()
如果 self 是一个目录则返回 True。
-
abstractmethod is_file()
如果 self 是一个文件则返回 True。
-
abstractmethod joinpath(child)
返回 self 中的 Traversable 子对象。
-
abstractmethod truediv(child)
返回 self 中的 Traversable 子对象。
-
abstractmethod open(mode='r', args, kwargs)
mode 可以为 'r' 或 'rb' 即以文本或二进制模式打开。 返回一个适用于读取的句柄(与 pathlib.Path.open 样同)。
当以文本模式打开时,接受与 io.TextIOWrapper 所接受的相同的编码格式形参。
-
read_bytes()
以字节串形式读取
self
的内容。 -
read_text (encoding=None)
以文本形式读取
self
的内容。
importlib.util {#importlib-util}
导入器的工具程序代码
importlib.util.MAGIC_NUMBER {#importlib-util-MAGIC-NUMBER}
代表字节码版本号的字节串。
importlib.util.cache_from_source (path , debug_override=None , optimization=None) {#importlib-util-cache-from-source-path-debug-override-None-optimization-None}
返回源 path 相关联的已编译字节码文件的路径。
importlib.util.source_from_cache (path) {#importlib-util-source-from-cache-path}
根据指向一个文件名的 path,返回相关联的源代码文件路径。 举例来说,如果 path 为 /foo/bar/pycache/baz.cpython-32.pyc 则返回的路径将是 /foo/bar/baz.py。 path 不需要已存在,但如果它未遵循 PEP 3147 或 PEP 488 的格式,则会引发 ValueError。 如果未定义 sys.implementation.cache_tag,则会引发 NotImplementedError。
importlib.util.decode_source(source_bytes) {#importlib-util-decode-source-source-bytes}
对代表源代码的字节串进行解码,并将其作为带有通用换行符的字符串返回(符合 importlib.abc.InspectLoader.get_source() 要求)。
importlib.util.resolve_name (name , package) {#importlib-util-resolve-name-name-package}
将模块的相对名称解析为绝对名称。
如果 name 前面没有句点,那就简单地返回 name。这样就能采用 importlib.util.resolve_name('sys', spec.parent) 之类的写法,而无需检查是否需要 package 参数。
如果 name 是一个相对模块名称但 package 为假值(如为 None 或空字符串)则会引发 ImportError。 如果相对名称离开了其所在的包(如为从 spam 包请求 ...bacon 的形式)则也会引发 ImportError。
importlib.util.find_spec (name , package=None) {#importlib-util-find-spec-name-package-None}
查找模块的 spec,可选择相对于指定的 package 名称。 如果该模块位于 sys.modules 中,则会返回 sys.modules[name].spec (除非 spec 为 None 或未设置,在此情况下则会引发 ValueError)。 在其他情况下将使用 sys.meta_path 进行搜索。 如果找不到任何 spec 则返回 None。
如果 name 为一个子模块(带有一个句点),则会自动导入父级模块。
name 和 package 的用法与 import_module() 相同。
importlib.util.module_from_spec (spec) {#importlib-util-module-from-spec-spec}
基于 spec 和 spec.loader.create_module 创建一个新模块。
如果 spec.loader.create_module 未返回 None,那么先前已存在的属性不会被重置。另外,如果 AttributeError 是在访问 spec 或设置模块属性时触发的,则不会触发 。
本函数比 types.ModuleType 创建新模块要好,因为用到 spec 模块设置了尽可能多的导入控制属性。
importlib.util.spec_from_loader(name, loader, *, origin=None, is_package=None) {#importlib-util-spec-from-loader-name-loader-origin-None-is-package-None}
一个工厂函数,用于创建基于加载器的 ModuleSpec 实例。参数的含义与 ModuleSpec 的相同。该函数会利用当前可用的 loader API,比如 InspectLoader.is_package(),以填充所有缺失的规格信息。
importlib.util.spec_from_file_location(name, location, *, loader=None, submodule_search_locations=None) {#importlib-util-spec-from-file-location-name-location-loader-None-submodule-search-locations-None}
一个工厂函数,根据文件路径创建 ModuleSpec 实例。缺失的信息将根据 spec 进行填补,利用加载器 API ,以及模块基于文件的隐含条件。
importlib.util.source_hash (source_bytes) {#importlib-util-source-hash-source-bytes}
以字节串的形式返回 source_bytes 的哈希值。基于哈希值的 .pyc 文件在头部嵌入了对应源文件内容的 source_hash()。
importlib.util._incompatible_extension_module_restrictions(*, disable_check) {#importlib-util-incompatible-extension-module-restrictions-disable-check}
一个可以暂时跳过扩展模块兼容性检查的上下文管理器。 在默认情况下该检查将被启用并且当在子解释器中导入单阶段初始化模块时该检查会失败。 如果多阶段初始化模块没有显式地支持针对子解释器的 GIL,那么当它在一个有自己的 GIL 的解释器中被导入时,该检查也会失败。
请注意该函数是为了适应一种不寻常的情况;这种情况可能最终会消失。 这很有可能不是你需要考虑的事情。
class importlib.util.LazyLoader(loader) {#class-importlib-util-LazyLoader-loader}
此类会延迟执行模块加载器,直至该模块有一个属性被访问到。
此类 仅仅 适用于定义 exec_module() 作为需要控制模块使用何种模块类型的加载器。 出于相同理由,加载器的 create_module() 方法必须返回 None 或其 class 属性可被改变并且不使用 槽位 的类型。 最后,用于替换已放入 sys.modules 的对象的模块将无法工作因为没有办法安全地在整个解释器中正确替换模块引用; 如果检测到这种替换则会引发 ValueError。
class importlib.util.LazyLoader(loader) {#class-importlib-util-LazyLoader-loader-2}
此类会延迟执行模块加载器,直至该模块有一个属性被访问到。
此类 仅仅 适用于定义 exec_module() 作为需要控制模块使用何种模块类型的加载器。 出于相同理由,加载器的 create_module() 方法必须返回 None 或其 class 属性可被改变并且不使用 槽位 的类型。 最后,用于替换已放入 sys.modules 的对象的模块将无法工作因为没有办法安全地在整个解释器中正确替换模块引用; 如果检测到这种替换则会引发 ValueError。
-
classmethod factory (loader)
一个返回创建延迟加载器的可调用对象的类方法。 这专门被用于加载器由类而不是实例来传入的场合。
举例 {#举例}
用编程方式导入 {#用编程方式导入}
要以编程方式导入一个模块,请使用 importlib.import_module()
检查某模块可否导入 {#检查某模块可否导入}
如果你需要在不实际执行导入的情况下确定某个模块是否可被导入,则你应当使用 importlib.util.find_spec()。
请注意如果 name 是一个子模块(即包含一个点号),则 importlib.util.find_spec() 将会导入父模块。
直接导入源码文件 {#直接导入源码文件}
这个专用方案应当谨慎使用:它是直接指明文件路径而不搜索 sys.path 的 import 语句的近似物。 应当首先考虑其他替代方案,比如在需要某个特定模块时修改 sys.path,或在运行某个 Python 得到正确的全局命名空间时使用 runpy.run_path()。
要直接从某个路径导入 Python 源文件,请使用以下写法:
实现延迟导入 {#实现延迟导入}
导入器的配置 {#导入器的配置}
对于导入的深度定制,通常你需要实现一个 importer。 这意味着同时管理 finder 和 loader 两方面。 对于查找器来说根据你的需求有两种类别可供选择: meta path finder 或 path entry finder。 前者你应当放到 sys.meta_path 而后者是使用 path entry hook 在 sys.path_hooks 上创建并与 sys.path 条目一起创建一个潜在的查找器。 下面的例子将向你演示如何注册自己的导入器供导入机制使用 (关于自行创建导入器,请阅读在本包内定义的相应类的文档):
importlib.import_module() 的近似实现 {#importlib-import-module-的近似实现}
导入过程本身是用 Python 代码实现的,这样就有可能通过 importlib 来对外公开大部分导入机制。 以下代码通过提供 importlib.import_module() 的近似实现来说明 importlib 所公开的几种 API:
参考资料 {#参考资料}
文章链接:
https://www.zywvvd.com/notes/coding/python/python-importlib/python-importlib/