5. 输入系统

一个python代码 module 通过以下过程访问另一个模块中的代码: importing 它。这个 import 语句是调用导入机制最常见的方法,但它不是唯一的方法。功能,例如 importlib.import_module() 内置 __import__() 也可用于调用导入机制。

这个 import 语句组合了两个操作;它搜索已命名的模块,然后将搜索结果绑定到本地作用域中的名称。的搜索操作 import 语句定义为对 __import__() 函数,带有适当的参数。的返回值 __import__() 用于执行 import 语句。见 import 用于该名称绑定操作的确切详细信息的语句。

直接调用 __import__() 仅执行模块搜索和(如果找到)模块创建操作。但可能会出现某些副作用,如导入父包和更新各种缓存(包括 sys.modules ),只有 import 语句执行名称绑定操作。

当一个 import 语句被执行,标准内置 __import__() 调用函数。调用导入系统的其他机制(例如 importlib.import_module() )可以选择绕过 __import__() 并使用自己的解决方案来实现导入语义。

当第一次导入模块时,python将搜索该模块,如果找到该模块,它将创建一个模块对象。 1, 正在初始化它。如果找不到命名模块,则 ModuleNotFoundError 提高了。当调用导入机制时,python实现了各种搜索命名模块的策略。这些策略可以通过使用下面部分描述的各种钩子进行修改和扩展。

在 3.3 版更改: 导入系统已更新,以完全实现 PEP 302 . 不再有任何隐含的导入机制-完整的输入系统通过 sys.meta_path . 此外,还实现了本机命名空间包支持(请参见 PEP 420

5.1. importlib

这个 importlib 模块为与导入系统交互提供了丰富的API。例如 importlib.import_module() 提供推荐的比内置API更简单的API __import__() 用于调用输入机器。请参阅 importlib 其他细节的类库文件。

5.2. 封装

python只有一种类型的模块对象,并且所有模块都是这种类型的,不管模块是用python、c还是其他什么方式实现的。为了帮助组织模块并提供命名层次结构,python有一个概念 packages .

您可以将包视为文件系统上的目录,将模块视为目录中的文件,但不要太直截了当地进行这种类比,因为包和模块不需要来自文件系统。在本文档中,我们将使用目录和文件的这种方便的类比。与文件系统目录一样,包是按层次结构组织的,包本身可能包含子包以及常规模块。

必须记住,所有的包都是模块,但并非所有的模块都是包。或者换句话说,包只是一种特殊的模块。具体来说,任何包含 __path__ 属性被视为包。

所有模块都有一个名称。子包名与其父包名之间用点分隔,类似于Python的标准属性访问语法。因此,您可能有一个名为 sys 还有一个叫 email ,它的子包名为 email.mime 子包中的一个模块 email.mime.text .

5.2.1. 普通包裹

python定义了两种类型的包, regular packagesnamespace packages . 常规包是传统包,因为它们存在于Python3.2和更早版本中。常规包通常实现为包含 __init__.py 文件。当导入常规包时,此 __init__.py 文件被隐式执行,它定义的对象被绑定到包命名空间中的名称。这个 __init__.py 文件可以包含任何其他模块可以包含的同一个python代码,并且在导入模块时,python将向该模块添加一些附加属性。

例如,以下文件系统布局定义了顶层 parent 包含三个子包的包::

parent/
    __init__.py
    one/
        __init__.py
    two/
        __init__.py
    three/
        __init__.py

输入 parent.one 将隐式执行 parent/__init__.pyparent/one/__init__.py . 后续输入 parent.twoparent.three 将执行 parent/two/__init__.pyparent/three/__init__.py 分别。

5.2.2. 命名空间包

命名空间包是由 portions ,其中每个部分向父包提供子包。部分可能位于文件系统的不同位置。也可以在zip文件、网络或python在导入期间搜索的任何其他位置找到部分。命名空间包可能直接与文件系统上的对象对应,也可能不直接与之对应;它们可能是没有具体表示的虚拟模块。

命名空间包不使用普通列表 __path__ 属性。相反,如果父包的路径(或 sys.path 对于顶级包)更改。

对于命名空间包,没有 parent/__init__.py 文件。事实上,可能有多个 parent 在导入搜索期间找到的目录,其中每个目录都由不同的部分提供。因此 parent/one 可能不会实际位于 parent/two . 在这种情况下,python将为顶层创建一个名称空间包 parent 无论何时导入它或它的某个子包。

也见 PEP 420 用于命名空间包规范。

5.3. 搜索

要开始搜索,python需要 fully qualified 正在导入的模块(或包的名称,但在本讨论中,差异无关紧要)。此名称可能来自 import 语句,或从参数到 importlib.import_module()__import__() 功能。

此名称将用于导入搜索的各个阶段,它可能是指向子模块的点式路径,例如。 foo.bar.baz . 在这种情况下,python首先尝试导入 foo 然后 foo.bar 最后 foo.bar.baz . 如果任何中间输入失败, ModuleNotFoundError 提高了。

5.3.1. 模块缓存

在导入搜索期间检查的第一个位置是 sys.modules .此映射用作以前导入的所有模块(包括中间路径)的缓存。所以如果 foo.bar.baz 以前导入过, sys.modules 将包含以下项: foofoo.barfoo.bar.baz . 每个键的值都将对应的模块对象。

在导入过程中,将在 sys.modules 如果存在的话,关联值就是满足导入的模块,过程就完成了。但是,如果值为 None 然后 ModuleNotFoundError 提高了。如果缺少模块名,python将继续搜索该模块。

sys.modules 是可写的。删除一个键可能不会破坏关联的模块(因为其他模块可能包含对它的引用),但它会使命名模块的缓存项无效,从而导致python在下次导入时重新搜索命名模块。密钥也可以分配给 None ,强制模块的下一个导入导致 ModuleNotFoundError .

但是要小心,就好像保留了对module对象的引用一样,使中的缓存项无效。 sys.modules ,然后重新导入命名模块,这两个模块对象将 not 一样。相比之下, importlib.reload() 将重用 same 模块对象,并通过重新运行模块的代码简单地重新初始化模块内容。

5.3.2. 取货机和加载器

如果在 sys.modules 然后调用python的导入协议来查找和加载模块。该协议由两个概念对象组成, findersloaders . 查找器的工作是确定它是否可以使用它知道的任何策略来查找命名模块。实现这两个接口的对象称为 importers -当他们发现可以加载请求的模块时,就会返回自己。

python包含许多默认的查找器和导入器。第一个知道如何定位内置模块,第二个知道如何定位冻结的模块。第三个默认查找器搜索 import path 用于模块。这个 import path 是可以命名文件系统路径或zip文件的位置列表。它还可以扩展到搜索任何可定位的资源,例如由URL标识的资源。

由于导入机制具有可扩展性,因此可以添加新的查找器来扩展模块搜索的范围和范围。

查找器实际上不加载模块。如果可以找到命名模块,则返回 module spec,模块的导入相关信息的封装,导入机制在加载模块时使用该信息。

以下各节将更详细地描述查找器和加载器的协议,包括如何创建和注册新协议以扩展导入机制。

在 3.4 版更改: 在之前的python版本中,finders返回 loaders 直接,而现在他们返回模块规格 包含 加载器。加载器在输入过程中仍在使用,但责任较轻。

5.3.3. 导入钩子

导入机制的设计是可扩展的;其主要机制是 import hooks . 有两种类型的导入挂钩: meta hooksimport path hooks .

在发生任何其他导入处理之前,在导入处理开始时调用元挂钩,而不是 sys.modules 缓存查找。这允许元钩子重写 sys.path 处理、冻结模块,甚至内置模块。通过将新的finder对象添加到 sys.meta_path ,如下所述。

导入路径挂钩作为 sys.path (或) package.__path__ )在遇到其关联路径项的位置进行处理。导入路径挂钩是通过将新的可调用文件添加到注册的 sys.path_hooks 如下所述。

5.3.4. 元路径

当在中找不到命名模块时 sys.modules ,python next搜索 sys.meta_path ,其中包含元路径查找器对象的列表。查询这些查找器是为了查看它们是否知道如何处理命名模块。元路径查找器必须实现名为 find_spec() 它有三个参数:名称、导入路径和(可选)目标模块。元路径查找器可以使用它想要确定是否可以处理命名模块的任何策略。

如果元路径查找器知道如何处理命名模块,它将返回spec对象。如果无法处理命名模块,则返回 None . 如果 sys.meta_path 处理到达其列表的末尾而不返回规范,然后 ModuleNotFoundError 提高了。引发的任何其他异常都只是向上传播,从而中止导入过程。

这个 find_spec() 使用两个或三个参数调用元路径查找器的方法。第一个是要导入的模块的完全限定名,例如 foo.bar.baz . 第二个参数是用于模块搜索的路径条目。对于顶级模块,第二个参数是 None ,但对于子模块或子包,第二个参数是父包的值 __path__ 属性。如果合适的话 __path__ 无法访问属性,a ModuleNotFoundError 提高了。第三个参数是一个现有的模块对象,稍后将作为加载的目标。导入系统仅在重新加载期间传入目标模块。

对于单个导入请求,可以多次遍历元路径。例如,假设所涉及的模块均未缓存,则导入 foo.bar.baz 将首先执行顶级导入,调用 mpf.find_spec("foo", None, None) 在每个元路径查找器上 (mpf )后 foo 已经导入, foo.bar 将通过再次遍历元路径导入,调用 mpf.find_spec("foo.bar", foo.__path__, None) . 一次 foo.bar 已导入,最终遍历将调用 mpf.find_spec("foo.bar.baz", foo.bar.__path__, None) .

一些元路径查找器只支持顶级导入。这些importers总会回来的 None 当除了 None 作为第二个参数传递。

python的默认值 sys.meta_path 有三个元路径查找器,一个知道如何导入内置模块,一个知道如何导入冻结模块,另一个知道如何从 import path (即 path based finder

在 3.4 版更改: 这个 find_spec() 替换元路径查找器的方法 find_module() ,现已弃用。虽然它将继续工作而不改变,导入机制将尝试它只有在查找工具不实现的情况下。 find_spec() .

5.4. 加载

如果发现模块规格,导入机制将在加载模块时使用它(及其包含的加载器)。下面是在导入的加载部分发生的情况的近似值:

module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    # It is assumed 'exec_module' will also be defined on the loader.
    module = spec.loader.create_module(spec)
if module is None:
    module = ModuleType(spec.name)
# The import-related module attributes get set here:
_init_module_attrs(spec, module)

if spec.loader is None:
    # unsupported
    raise ImportError
if spec.origin is None and spec.submodule_search_locations is not None:
    # namespace package
    sys.modules[spec.name] = module
elif not hasattr(spec.loader, 'exec_module'):
    module = spec.loader.load_module(spec.name)
    # Set __loader__ and __package__ if missing.
else:
    sys.modules[spec.name] = module
    try:
        spec.loader.exec_module(module)
    except BaseException:
        try:
            del sys.modules[spec.name]
        except KeyError:
            pass
        raise
return sys.modules[spec.name]

请注意以下详细信息:

  • 如果中存在具有给定名称的现有模块对象 sys.modules ,import将已返回它。

  • 模块将存在于 sys.modules 在加载程序执行模块代码之前。这一点至关重要,因为模块代码可以(直接或间接)导入自身;将其添加到 sys.modules 预先防止在最坏的情况下无限递归,在最好的情况下多次加载。

  • 如果加载失败,将从中删除失败的模块(并且仅删除失败的模块) sys.modules . 已经在中的任何模块 sys.modules 缓存以及作为副作用成功加载的任何模块都必须保留在缓存中。这与重新加载相反,即使失败的模块仍留在其中 sys.modules .

  • 在创建模块之后但在执行之前,导入机制设置与导入相关的模块属性(上面伪代码示例中的“初始化模块属性”),如 later section .

  • 模块执行是加载的关键时刻,在该时刻填充模块的命名空间。执行完全委托给加载器,加载器决定填充什么以及如何填充。

  • 在加载期间创建并传递给exec_module()的模块可能不是在导入结束时返回的模块。 2.

在 3.4 版更改: 输入系统承担了加载器的样板任务。这些之前是由 importlib.abc.Loader.load_module() 方法。

5.4.1. 加载器

模块加载程序提供加载的关键功能:模块执行。输入机器称 importlib.abc.Loader.exec_module() 方法,只使用一个参数,即要执行的模块对象。从返回的任何值 exec_module() 被忽略。

加载器必须满足以下要求:

  • 如果模块是python模块(而不是内置模块或动态加载的扩展),加载程序应该在模块的全局名称空间中执行模块的代码。 (module.__dict__

  • 如果加载器不能执行模块,它应该引发 ImportError ,尽管在 exec_module() 将被传播。

在许多情况下,finder和loader可以是同一个对象;在这种情况下, find_spec() 方法只返回一个将加载程序设置为的规范 self .

模块加载程序可以通过实现 create_module() 方法。它接受一个参数module spec,并返回要在加载期间使用的新module对象。 create_module() 不需要在模块对象上设置任何属性。如果方法返回 None ,导入机制将自行创建新模块。

3.4 新版功能: 这个 create_module() 加载器的方法。

在 3.4 版更改: 这个 load_module() 方法被替换为 exec_module() 导入机制承担了所有的加载任务。

为了与现有加载器兼容,导入机制将使用 load_module() 加载程序的方法(如果存在且加载程序也不实现) exec_module() .然而, load_module() 已弃用,加载程序应实现 exec_module() 相反。

这个 load_module() 方法除了执行模块之外,还必须实现上面描述的所有样板加载功能。所有相同的约束条件都适用,但还有一些附加说明:

  • 如果中存在具有给定名称的现有模块对象 sys.modules ,加载程序必须使用该现有模块。(否则) importlib.reload() 将无法正常工作。)如果 sys.modules ,加载程序必须创建一个新的模块对象并将其添加到 sys.modules .

  • 模块 must 存在于 sys.modules 在加载程序执行模块代码之前,防止无限递归或多次加载。

  • 如果加载失败,加载程序必须删除它插入的任何模块 sys.modules ,但必须移除 only 只有在加载程序本身已显式加载模块的情况下,才是出现故障的模块。

在 3.5 版更改: A DeprecationWarning 提高时 exec_module() 定义但 create_module() 不是。

在 3.6 版更改: ImportError 提高时 exec_module() 定义但 create_module() 不是。

5.4.2. 子模块

当使用任何机构加载子模块时(例如 importlib API importimport-from 声明或内置 __import__() )在父模块的名称空间中放置到子模块对象的绑定。例如,如果包 spam 有子模块 foo ,导入后 spam.foospam 将具有属性 foo 它绑定到子模块。假设您有以下目录结构:

spam/
    __init__.py
    foo.py
    bar.py

spam/__init__.py 其中包含以下行:

from .foo import Foo
from .bar import Bar

然后执行以下操作将名称绑定到 foobarspam 模块:

>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.bar
<module 'spam.bar' from '/tmp/imports/spam/bar.py'>

考虑到Python熟悉的名称绑定规则,这看起来可能令人惊讶,但实际上它是导入系统的一个基本特性。不变的保持是 sys.modules['spam']sys.modules['spam.foo'] (正如您在上述导入之后所做的那样),后者必须显示为 foo 前者的属性。

5.4.3. 模块规格

导入机制在输入过程中,特别是在装载前,使用了有关每个模块的各种信息。大多数信息对所有模块都是通用的。模块规范的目的是在每个模块的基础上封装与导入相关的信息。

在导入期间使用规范允许在导入系统组件之间传输状态,例如在创建模块规范的查找器和执行模块规范的加载程序之间。最重要的是,它允许导入机制执行装载的样板操作,而没有模块规格,加载器就有责任。

模块的规范公开为 __spec__ 模块对象的属性。参见 ModuleSpec 有关模块规范内容的详细信息。

3.4 新版功能.

5.4.5. module.__path__

根据定义,如果模块具有 __path__ 属性,它是一个包。

包裹的 __path__ 属性在其子包的导入过程中使用。在导入机制中,它的功能与 sys.path ,即提供导入期间搜索模块的位置列表。然而, __path__ 通常比 sys.path .

__path__ 必须是字符串的iterable,但它可能是空的。使用相同的规则 sys.path 也适用于程序包 __path__sys.path_hooks (如下所述)在遍历包的 __path__ .

包裹的 __init__.py 文件可以设置或更改包的 __path__ 属性,这通常是在 PEP 420 . 通过 PEP 420 ,命名空间包不再需要提供 __init__.py 仅包含 __path__ 操作码;导入机制自动设定 __path__ 对于命名空间包正确。

5.4.6. 模块转发

默认情况下,所有模块都有一个可用的repr,但是根据上面设置的属性,在模块的规范中,您可以更明确地控制模块对象的repr。

如果模块有规格 (__spec__ )导入机制将尝试从中生成报告。如果失败或没有规范,导入系统将使用模块上可用的任何信息创建默认的repr。它将尝试使用 module.__name__module.__file__module.__loader__ 作为repr的输入,对于缺少的任何信息都使用默认值。

具体规则如下:

  • 如果模块具有 __spec__ 属性,规范中的信息用于生成repr。将查询“name”、“loader”、“origin”和“has-location”属性。

  • 如果模块具有 __file__ 属性,它用作模块的repr的一部分。

  • 如果模块没有 __file__ 但确实有 __loader__ 那不是 None ,然后将加载程序的repr用作模块的repr的一部分。

  • 否则,只需使用模块的 __name__ 在RPR中。

在 3.4 版更改: 使用 loader.module_repr() 已弃用,导入机制现在使用模块规范生成模块报告。

为了向后兼容python 3.3,模块repr将通过调用加载程序的 module_repr() 方法,如果已定义,则在尝试上述任一方法之前。但是,该方法已被弃用。

5.4.7. 缓存字节码无效

在Python从 .pyc 文件时,它会检查缓存是否与源同步最新 .py 文件。默认情况下,Python通过在写入源文件时将其上次修改的时间戳和大小存储在缓存文件中来实现这一点。在运行时,导入系统然后通过对照源的元数据检查缓存文件中存储的元数据来验证缓存文件。

python还支持“基于散列的”缓存文件,它存储源文件内容的散列,而不是其元数据。基于hash的变量有两种 .pyc 文件:已选中和未选中。用于检查基于hash的 .pyc 文件,python通过散列源文件并将结果散列与缓存文件中的散列进行比较来验证缓存文件。如果发现选中的基于hash的缓存文件无效,则python将重新生成该文件,并写入新的选中的基于hash的缓存文件。对于未选中的基于hash的 .pyc 文件,python只是假设缓存文件存在时是有效的。基于hash .pyc 文件验证行为可能被重写 --check-hash-based-pycs flag。

在 3.7 版更改: 添加了基于hash的 .pyc 文件夹。以前,python只支持基于时间戳的字节码缓存无效化。

5.5. 基于路径的查找器

如前所述,python附带了几个默认的元路径查找器。其中一个叫 path based finder (PathFinder )搜索 import path ,其中包含 path entries . 每个路径条目都指定了一个搜索模块的位置。

基于路径的查找器本身不知道如何导入任何内容。相反,它遍历各个路径条目,将它们与知道如何处理特定类型路径的路径条目查找器关联起来。

默认的路径条目查找器集实现了在文件系统上查找模块、处理特殊文件类型(如python源代码)的所有语义。 (.py 文件),python字节代码 (.pyc 文件)和共享库(例如 .so 文件)。当得到 zipimport 模块在标准库中,默认路径条目查找器还处理从zipfiles加载所有这些文件类型(共享库除外)。

路径条目不必局限于文件系统位置。它们可以引用URL、数据库查询或任何其他可以指定为字符串的位置。

基于路径的查找工具提供了额外的钩子和协议,以便您可以扩展和自定义可搜索路径条目的类型。例如,如果希望支持路径条目作为网络URL,可以编写一个钩子来实现HTTP语义,以便在Web上查找模块。这个钩子(可调用的)将返回 path entry finder 支持下面描述的协议,该协议随后用于从Web获取模块的加载程序。

警告:本节和前一节都使用了 取景器 ,用术语区分它们 meta path finderpath entry finder . 这两种类型的查找器非常相似,支持类似的协议,并且在导入过程中以类似的方式工作,但请记住,它们有细微的不同。特别是,元路径查找器在导入过程的开始运行,就像键控的 sys.meta_path 遍历。

相反,路径入口查找器在某种意义上是基于路径的查找器的实现细节,实际上,如果要从 sys.meta_path ,不会调用任何路径条目查找器语义。

5.5.1. 路径入口查找器

这个 path based finder 负责查找和加载位置由字符串指定的python模块和包 path entry . 大多数路径条目命名文件系统中的位置,但不必局限于此。

作为元路径查找器, path based finder 实现 find_spec() 但是,它公开了其他钩子,这些钩子可用于自定义如何从 import path .

三个变量由 path based findersys.pathsys.path_hookssys.path_importer_cache . 这个 __path__ 还使用包对象上的属性。这些为导入机制的定制提供了额外的方式。

sys.path 包含为模块和包提供搜索位置的字符串列表。它是从初始化的 PYTHONPATH 环境变量和各种其他特定于安装和实现的默认值。条目 sys.path 可以命名文件系统上的目录、zip文件以及可能的其他“位置”(请参见 site 模块)应该搜索模块,如URL或数据库查询。上只应存在字符串和字节 sys.path ;忽略所有其他数据类型。字节条目的编码由个人决定。 path entry finders .

这个 path based finder 是一个 meta path finder 因此导入机制开始 import path 通过调用基于路径的查找器进行搜索 find_spec() 方法如前所述。当 path 参数 find_spec() 如果给定,它将是要遍历的字符串路径列表-通常是包的 __path__ 该包中导入的属性。如果 path 论证是 None ,这表示顶级导入和 sys.path 使用。

基于路径的查找器迭代搜索路径中的每个条目,并为每个条目查找适当的 path entry finder (PathEntryFinder )对于路径条目。因为这可能是一个昂贵的操作(例如 stat() 调用此搜索的开销),基于路径的查找器维护一个缓存映射路径条目到路径条目查找器。此缓存维护在 sys.path_importer_cache (尽管名称不同,此缓存实际上存储查找器对象,而不限于 importer 对象)。以这种方式,昂贵的搜索 path entry 位置 path entry finder 只需要做一次。用户代码可以从中自由删除缓存项 sys.path_importer_cache 强制基于路径的查找器再次执行路径条目搜索 3.

如果缓存中不存在路径条目,则基于路径的查找器将迭代 sys.path_hooks . 每一个 path entry hooks 在此列表中,使用单个参数调用要搜索的路径条目。此可调用项可以返回 path entry finder 可以处理路径入口,或者它可能会引发 ImportError . 安 ImportError 被基于路径的查找器用来表示钩子找不到 path entry finder 为此 path entry . 该异常被忽略,并且 import path 迭代继续。钩子应该期望一个字符串或字节对象;字节对象的编码取决于钩子(例如,它可能是文件系统编码、UTF-8或其他东西),如果钩子不能解码参数,它应该 ImportError .

如果 sys.path_hooks 迭代以否结束 path entry finder 然后返回基于路径的查找器 find_spec() 方法将存储 None 在里面 sys.path_importer_cache (指示此路径条目没有查找程序)并返回 None ,表示 meta path finder 找不到模块。

如果A path entry finder is 由其中一个返回 path entry hook 可调用的 sys.path_hooks ,然后使用以下协议向finder请求模块规范,然后在加载模块时使用该规范。

当前工作目录(由空字符串表示)的处理方式与 sys.path . 首先,如果发现当前工作目录不存在,则没有值存储在 sys.path_importer_cache . 第二,对于每个模块查找,都会重新查找当前工作目录的值。第三,用于 sys.path_importer_cache 并返回 importlib.machinery.PathFinder.find_spec() 将是实际的当前工作目录,而不是空字符串。

5.5.2. 路径入口查找器协议

为了支持模块和初始化包的导入,以及为命名空间包贡献部分,路径条目查找器必须实现 find_spec() 方法。

find_spec() 接受两个参数:要导入的模块的完全限定名和(可选)目标模块。 find_spec() 返回模块的完全填充的规范。此规范将始终设置“加载器”(有一个例外)。

向导入机器指示规范表示命名空间 portion 路径条目查找器将“SUBMODULE_SEARCH_LOCATIONS”设置为包含该部分的列表。

在 3.4 版更改: find_spec() 替换 find_loader()find_module() ,两者现在都已弃用,但将在以下情况下使用 find_spec() 未定义。

旧的路径条目查找器可能实现这两个已弃用的方法之一,而不是 find_spec() . 为了向后兼容,这些方法仍然受到尊重。然而,如果 find_spec() 在路径条目查找器上实现,将忽略旧方法。

find_loader() 接受一个参数,即要导入的模块的完全限定名称。 find_loader() 返回一个二元组,其中第一项是加载器,第二项是命名空间 portion

为了与导入协议的其他实现向后兼容,许多路径条目查找器也支持相同的传统 find_module() 元路径查找器支持的方法。但是路径入口查找器 find_module() 从未使用 path 参数(它们应该记录从初始调用到路径挂钩的适当路径信息)。

这个 find_module() 不推荐使用路径条目查找器上的方法,因为它不允许路径条目查找器向命名空间包提供部分内容。如果两者 find_loader()find_module() 存在于路径条目查找器上,导入系统将始终调用 find_loader() 优先于 find_module() .

5.6. 更换标准输入系统

替换整个导入系统最可靠的机制是删除 sys.meta_path ,将它们完全替换为自定义的元路径挂钩。

如果只更改import语句的行为而不影响其他访问导入系统的API是可以接受的,则替换内置的 __import__() 功能可能足够。这种技术也可以在模块级别上使用,只改变该模块中导入语句的行为。

为了有选择地防止在元路径的早期从钩子导入某些模块(而不是完全禁用标准导入系统),只需 ModuleNotFoundError 直接来自 find_spec() 而不是返回 None . 后者表示应该继续元路径搜索,而引发异常会立即终止元路径搜索。

5.7. 包相对导入

相对导入使用前导点。一个前导点表示从当前包开始的相对导入。两个或多个前导点表示对当前包的父级的相对导入,第一个点后每个点一级。例如,给定以下包布局:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

在任何一个 subpackage1/moduleX.pysubpackage1/__init__.py ,以下是有效的相对导入:

from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo

绝对进口可以使用 import <>from <> import <> 语法,但是相对导入只能使用第二种形式;原因是:

import XXX.YYY.ZZZ

应该暴露 XXX.YYY.ZZZ 作为可用表达式,但.moduley不是有效表达式。

5.8. 针对 __main__ 的注意事项

这个 __main__ 模块是与Python的导入系统相关的特殊情况。如前所述 elsewhere , the __main__ 模块在解释器启动时直接初始化,就像 sysbuiltins . 但是,与这两个模块不同,它并不能严格限定为内置模块。这是因为 __main__ 初始化取决于调用解释器时使用的标志和其他选项。

5.8.1. __main__.__spec__

取决于如何 __main__ 初始化, __main__.__spec__ 得到适当的设置或 None .

当python以 -m 选项, __spec__ 设置为相应模块或包的模块规格。 __spec____main__ 模块作为执行目录、zipfile或其他 sys.path 条目。

the remaining cases __main__.__spec__ 设置为 None ,作为用于填充 __main__ 与可导入模块不直接对应:

  • 交互式提示

  • -c 选项

  • 从stdin运行

  • 直接从源或字节码文件运行

注意 __main__.__spec__ 总是 None 在最后一个案例中, 即使 技术上,该文件可以作为模块直接导入。使用 -m 如果在中需要有效的模块元数据,则切换 __main__ .

还要注意,即使在 __main__ 对应于可导入模块和 __main__.__spec__ 相应地设置,它们仍然被考虑 不同的 模块。这是因为事实上 if __name__ == "__main__": 仅当模块用于填充 __main__ 命名空间,而不是在正常导入期间。

5.9. 公开问题

有一张图表会很好。

xxx*(import_machinery.rst)一节专门介绍模块和包的属性,或者扩展或替换数据模型参考页中的相关条目,怎么样?

类库手册中的xxx runpy、pkgutil等都应该在顶部找到指向新导入系统部分的“see also”链接。

增加更多关于不同方式的解释 __main__ 初始化了吗?

XXX添加更多信息 __main__ 怪癖/陷阱(即复制自 PEP 395

5.10. 工具书类

从Python早期开始,输入机器就有了很大的发展。原文 specification for packages 仍然可以阅读,尽管在编写该文档之后,一些细节发生了变化。

原始规范 sys.meta_pathPEP 302 ,后续扩展 PEP 420 .

PEP 420 介绍 namespace packages 对于Python 3.3。 PEP 420 还介绍了 find_loader() 作为替代方案 find_module() .

PEP 366 描述添加的 __package__ 用于在主模块中显式相对导入的属性。

PEP 328 引入绝对和显式相对输入,并初步提出 __name__ 语义学 PEP 366 最终会指定 __package__ .

PEP 338 将执行模块定义为脚本。

PEP 451 在spec对象中添加每个模块导入状态的封装。它还将加载器的大部分样板任务卸载到输入机器上。这些更改允许取消导入系统中几个API的预测,也允许向查找器和加载器添加新方法。

脚注

1

types.ModuleType .

2

importlib实现避免直接使用返回值。相反,它通过在中查找模块名来获取模块对象 sys.modules . 这的间接影响是,导入的模块可以在 sys.modules . 这是特定于实现的行为,不能保证在其他Python实现中工作。

3

在旧代码中,可以找到 imp.NullImportersys.path_importer_cache . 建议将代码更改为使用 None 相反。见 移植python代码 了解更多详细信息。