python / 3.7.2rc1 / all / reference-import.html

5. 导入系统

module中的 Python 代码passimporting的过程可以访问另一模块中的代码。 import语句是调用导入机制的最常用方法,但不是唯一的方法。诸如importlib.import_module()和内置import()之类的Function也可以用于调用导入机制。

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

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

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

首次导入模块时,Python 会搜索该模块,如果找到该模块,它将创建一个模块对象[1],对其进行初始化。如果找不到命名的模块,则会引发ModuleNotFoundError。调用导入机制时,Python 实现了各种策略来搜索命名模块。可以使用以下各节中描述的各种钩子来修改和扩展这些策略。

在版本 3.3 中进行了更改:导入系统已更新,以完全实现 PEP 302的第二阶段。不再有任何隐式导入机制-完整的导入系统passsys.meta_path公开。此外,还实现了本地名称空间包支持(请参见 PEP 420)。

5.1. importlib

importlib模块提供了用于与导入系统进行交互的丰富 API。例如,importlib.import_module()提供了一个推荐的,比内置import()更简单的 API 来调用导入机制。有关更多详细信息,请参见importlib库文档。

5.2. Packages

Python 只有一种类型的模块对象,并且所有模块都是这种类型的,而不管该模块是用 Python,C 还是其他方式实现的。为了帮助组织模块并提供命名层次结构,Python 具有packages的概念。

您可以将软件包视为文件系统上的目录,而将模块视为目录中的文件,但是不要从字面上看这样的类比,因为软件包和模块不需要源自文件系统。为了本文档的目的,我们将使用目录和文件的这种便利类比。像文件系统目录一样,程序包是按层次结构组织的,程序包本身可能包含子程序包以及常规模块。

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

所有模块都有一个名称。子包名称与它们的父包名称之间用点分隔,类似于 Python 的标准属性访问语法。因此,您可能有一个名为sys的模块和一个名为email的包,而后者又有一个名为email.mime的子包,并且在该子包中有一个名为email.mime.text的模块。

5.2.1. 常规包装

Python 定义了两种类型的包regular packagesnamespace packages。常规软件包是 Python 3.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__属性不使用普通列表。相反,他们使用自定义的可迭代类型,如果其父包(或顶级包的路径为__)的路径发生更改,它将在该包内的下一次导入try时自动对包部分进行新搜索。

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

另请参阅 PEP 420以获取名称空间包规范。

5.3. Searching

要开始搜索,Python 需要导入模块(或包,但出于讨论目的,差异并不重要)的fully qualified名称。此名称可能来自import语句的各种参数,也可能来自importlib.import_module()import()函数的参数。

此名称将在导入搜索的各个阶段中使用,并且可能是子模块(例如, foo.bar.baz.在这种情况下,Python 首先try导入foo,然后是foo.bar,最后是foo.bar.baz.如果任何中间导入失败,则引发ModuleNotFoundError

5.3.1. 模块缓存

导入搜索期间检查的第一个位置是sys.modules。此 Map 用作先前已导入的所有模块(包括中间路径)的缓存。因此,如果先前已导入foo.bar.baz,则sys.modules将包含foofoo.barfoo.bar.baz的条目。每个键都有对应的模块对象作为其值。

导入期间,将在sys.modules中查找模块名称,如果存在,则关联的值是满足导入要求的模块,然后过程完成。但是,如果值为None,则引发ModuleNotFoundError。如果缺少模块名称,Python 将 continue 搜索模块。

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

但是要当心,就像您保留对模块对象的引用一样,在sys.modules中使它的缓存条目无效,然后重新导入命名模块,这两个模块对象将相同。相反,importlib.reload()将重用* same *模块对象,并pass重新运行模块的代码来简单地重新初始化模块内容。

5.3.2. 查找器和装载机

如果在sys.modules中找不到命名的模块,那么将调用 Python 的 import 协议来查找和加载该模块。该协议由两个概念对象findersloaders组成。查找器的工作是确定是否可以使用其知道的任何策略来找到命名模块。实现这两个接口的对象称为importers-当发现可以加载请求的模块时,它们将返回自身。

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

导入机制是可扩展的,因此可以添加新的查找器以扩展模块搜索的范围和范围。

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

以下各节更详细地介绍了查找程序和装载程序的协议,包括如何创建和注册新协议以扩展导入机制。

在 3.4 版中进行了更改:在 Python 的早期版本中,查找器直接返回loaders,而现在它们返回的模块规范“包含”加载程序。导入期间仍使用装载机,但责任较小。

5.3.3. import 钩子

import 机械设计为可扩展的;主要的机制是* import hooks 。有两种类型的导入钩子: meta 钩子 import path 钩子*。

在执行任何其他导入处理(而不是sys.modules缓存)之前,在导入处理开始时调用元钩子。这允许 meta 钩子覆盖sys.path处理,冻结的模块甚至内置模块。pass将新的查找器对象添加到sys.meta_path来注册元钩子,如下所述。

导入路径钩子是在sys.path(或package.__path__)处理中遇到它们的关联路径项的位置调用的。如下所述,pass向sys.path_hooks添加新的可调用对象来注册导入路径钩子。

5.3.4. 元路径

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

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

使用两个或三个参数调用元路径查找器的find_spec()方法。第一个是要导入的模块的标准名称,例如foo.bar.baz。第二个参数是用于模块搜索的路径条目。对于顶级模块,第二个参数是None,但是对于子模块或子包,第二个参数是父包的__path__属性的值。如果无法访问适当的__path__属性,则会引发ModuleNotFoundError。第三个参数是一个现有的模块对象,它将作为以后加载的目标。导入系统仅在重新加载期间传入目标模块。

对于单个导入请求,可以多次遍历元路径。例如,假设尚未缓存任何涉及的模块,则导入foo.bar.baz将首先执行顶级导入,在每个元路径查找器(mpf)上调用mpf.find_spec("foo", None, None)。导入foo之后,将pass再次遍历元路径(调用mpf.find_spec("foo.bar", foo.__path__, None))来导入foo.barfoo.bar导入后,finally遍历将调用mpf.find_spec("foo.bar.baz", foo.bar.__path__, None)

一些元路径查找器仅支持顶级导入。当将None以外的任何内容作为第二个参数传递时,这些导入器将始终返回None

Python 的默认sys.meta_path具有三个元路径查找器,一个知道如何导入内置模块,一个知道如何导入冻结的模块,另一个知道如何从import path(即基于路径的查找器)导入模块。

在版本 3.4 中进行了更改:元路径查找器的find_spec()方法替换了find_module(),现在已弃用。虽然它可以 continue 工作而无需进行任何更改,但是只有在查找器未实现find_spec()的情况下,导入机制才会try使用它。

5.4. Loading

如果找到模块规范,则导入机械将在加载模块时使用它(及其包含的加载器)。这是在导入的加载部分期间发生的近似情况:

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]

请注意以下详细信息:

Note

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

  • 加载程序执行模块代码之前,该模块将存在于sys.modules中。这很关键,因为模块代码可以(直接或间接)导入自身。事先将其添加到sys.modules可以防止在最坏的情况下实现无限递归,而在最好的情况下则可以避免多次加载。

  • 如果加载失败,则从sys.modules中删除发生故障的模块-仅有发生故障的模块。 sys.modules缓存中已经存在的任何模块,以及作为副作用成功加载的任何模块,都必须保留在缓存中。这与重新加载相反,在重新加载中,即使发生故障的模块也保留在sys.modules中。

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

  • 模块执行是加载的关键 Moment,模块的名称空间被填充到模块中。执行完全委托给加载程序,由加载程序决定填充哪些内容以及如何填充。

  • 加载期间创建并传递给 exec_module()的模块可能不是 import [2]末尾返回的模块。

在版本 3.4 中进行了更改:导入系统已接管了装载机的样板工作。这些以前是passimportlib.abc.Loader.load_module()方法执行的。

5.4.1. Loaders

模块加载器提供了加载的关键Function:模块执行。导入机制使用单个参数调用importlib.abc.Loader.exec_module()方法,以执行模块对象。从exec_module()返回的任何值都将被忽略。

装载机必须满足以下要求:

Note

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

  • 如果加载程序无法执行该模块,则应引发ImportError,尽管exec_module()期间引发的任何其他异常也会传播。

在许多情况下,取景器和装载器可以是同Pair象;在这种情况下,find_spec()方法将仅返回一个将 loader 设置为self的规范。

模块加载器可以pass实现create_module()方法来选择在加载过程中创建模块对象。它使用一个参数,即模块规范,并返回要在加载期间使用的新模块对象。 create_module()不需要在模块对象上设置任何属性。如果该方法返回None,则导入机制将自己创建新模块。

3.4 版中的新Function:加载程序的create_module()方法。

在版本 3.4 中进行了更改:load_module()方法被exec_module()代替,并且 import 机械承担了所有样板工作的装载职责。

为了与现有加载程序兼容,如果导入机械存在并且加载程序也未实现exec_module(),则导入机制将使用load_module()方法。但是,load_module()已被弃用,装入程序应改为实现exec_module()

load_module()方法除了执行模块外,还必须实现上述所有样板加载Function。所有相同的约束均适用,并作一些补充说明:

Note

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

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

  • 如果加载失败,则加载器必须删除已插入sys.modules的所有模块,但必须 移除发生故障的模块,并且仅在加载器本身已明确加载模块的情况下。

在版本 3.5 中进行了更改:定义exec_module()但未定义create_module()时会引发DeprecationWarning

在版本 3.6 中进行了更改:定义exec_module()但未定义create_module()时会引发ImportError

5.4.2. Submodules

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

spam/
    __init__.py
    foo.py
    bar.py

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

from .foo import Foo
from .bar import Bar

然后执行以下命令,将名称绑定到spam模块中的foobar

>>> 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 熟悉的名称绑定规则,这似乎令人惊讶,但这实际上是导入系统的基本Function。不变的条件是,如果您拥有sys.modules['spam']sys.modules['spam.foo'](就像在上面的导入之后一样),则后者必须作为前者的foo属性出现。

5.4.3. 模块规格

导入机制在导入期间(尤其是在加载之前)会使用有关每个模块的各种信息。大多数信息是所有模块共有的。模块规范的目的是在每个模块的基础上封装与导入相关的信息。

在导入过程中使用规范可以在导入系统组件之间转移状态,例如在创建模块规范的查找程序与执行模块规范的加载程序之间。最重要的是,它允许 import 机械执行加载的样板操作,而如果没有模块规格,则装载机应承担责任。

模块的规范在模块对象上显示为__spec__属性。有关模块规范内容的详细信息,请参见ModuleSpec

3.4 版的新Function。

5.4.4. 导入相关的模块属性

导入机制会在加载程序执行模块之前,根据模块规范在每个模块对象上填充这些属性。

  • __name__

    • __name__属性必须设置为模块的标准名称。此名称用于唯一地标识导入系统中的模块。
  • __loader__

    • __loader__属性必须设置为导入机械在加载模块时使用的加载器对象。这主要用于自省,但可用于其他特定于加载程序的Function,例如获取与加载程序关联的数据。
  • __package__

    • 必须设置模块的__package__属性。它的值必须是字符串,但可以与其__name__相同。当模块是软件包时,其__package__值应设置为__name__。当模块不是软件包时,对于顶级模块或子模块,应将__package__设置为空字符串,并将其设置为父软件包的名称。有关更多详细信息,请参见 PEP 366

PEP 366所定义,此属性用于代替__name__来计算主要模块的显式相对导入。预期具有与__spec__.parent相同的值。

在版本 3.6 中更改:__package__的值应与__spec__.parent相同。

如果未定义__package__,则将__spec__.parent用作后备。

3.4 版的新Function。

在版本 3.6 中更改:未定义__package__时,__spec__.parent用作后备。

  • __path__
    • 如果模块是程序包(常规或名称空间),则必须设置模块对象的__path__属性。该值必须是可迭代的,但如果__path__没有进一步的意义,则可以为空。如果__path__不为空,则迭代时必须产生字符串。 below给出了有关__path__语义的更多详细信息。

非软件包模块不应具有__path__属性。

  • __file__

  • __cached__

    • __file__是可选的。如果设置,则此属性的值必须是字符串。如果导入系统没有语义(例如,从数据库加载的模块),则可以选择不设置__file__

如果设置了__file__,则设置__cached__属性也可能是合适的,该属性是代码的任何编译版本(例如字节编译文件)的路径。该文件不需要存在即可设置此属性。该路径可以简单地指向已编译文件的存在位置(请参见 PEP 3147)。

5.4.5. module.path

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

包的__path__属性在导入其子包期间使用。在导入机制中,其Function与sys.path大致相同,即提供了导入期间要搜索模块的位置列表。但是,__path__通常比sys.path受更多约束。

__path__必须是字符串的可迭代对象,但它可以为空。用于sys.path的相同规则也适用于程序包的__path__,而遍历程序包__path__时将参考sys.path_hooks(如下所述)。

包的__init__.py文件可以设置或更改包的__path__属性,这通常是在 PEP 420之前实现名称空间包的方式。随着 PEP 420的采用,命名空间包不再需要提供仅包含__path__操作代码的__init__.py文件。导入机制会自动为名称空间包正确设置__path__

5.4.6. 模块代表

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

如果模块具有规格(__spec__),则导入机制将try从中生成一个 repr。如果失败或没有规范,导入系统将使用模块上可用的任何信息来制作默认代表。它将try使用module.__name__module.__file__module.__loader__作为 repr 的 Importing,缺少任何信息的默认值。

以下是使用的确切规则:

Note

  • 如果模块具有__spec__属性,则使用规范中的信息来生成 repr。请查阅“名称”,“装载程序”,“来源”和“ has_location”属性。

  • 如果模块具有__file__属性,则该属性将用作模块的代表。

  • 如果模块没有__file__但没有__loader__而不是None,则将加载程序的 repr 用作模块 repr 的一部分。

  • 否则,只需在代表中使用模块的__name__

在版本 3.4 中进行了更改:不建议使用loader.module_repr(),导入机制现在使用模块规范来生成模块代表。

为了与 Python 3.3 向后兼容,在try上述任何一种方法之前,将pass调用加载程序的module_repr()方法(如果已定义)来生成模块 repr。但是,不建议使用该方法。

5.4.7. 缓存的字节码无效

在 Python 从.pyc文件加载缓存的字节码之前,它会检查缓存是否与源.py文件是最新的。默认情况下,Python pass在写入时将源的最后修改的时间戳和大小存储在缓存文件中来实现此目的。然后,在运行时,导入系统pass对照源的元数据检查缓存文件中存储的元数据来验证缓存文件。

Python 还支持“基于哈希的”缓存文件,该文件存储源文件内容的哈希而不是元数据。基于哈希的.pyc文件有两种变体:选中和未选中。对于已检查的基于哈希的.pyc文件,Python pass对源文件进行哈希处理并将生成的哈希值与缓存文件中的哈希值进行比较来验证缓存文件。如果发现一个基于检查的基于哈希的缓存文件无效,Python 将重新生成该文件并编写一个新的基于检查的基于哈希的缓存文件。对于未经检查的基于散列的.pyc文件,Python 只是假设该高速缓存文件有效(如果存在)。基于哈希的.pyc文件验证行为可能会被--check-hash-based-pycs标志覆盖。

在 3.7 版中进行了更改:添加了基于哈希的.pyc文件。以前,Python 仅支持字节码缓存基于时间戳的失效。

5.5. 基于路径的查找器

如前所述,Python 附带了几个默认的元路径查找器。其中一个称为基于路径的查找器(PathFinder),搜索import path,其中包含path entries的列表。每个路径条目都为搜索模块的位置命名。

基于路径的查找器本身不知道如何导入任何内容。取而代之的是,它遍历各个路径条目,并将每个路径条目与知道如何处理该特定类型路径的路径条目查找器相关联。

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

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

基于路径的查找器提供了其他钩子和协议,因此您可以扩展和自定义可搜索路径条目的类型。例如,如果要支持路径条目作为网络 URL,则可以编写一个实现 HTTP 语义的钩子,以在 Web 上查找模块。该钩子(可调用)将返回支持以下协议的路径查找器,然后将其用于从 Web 上获取模块的加载程序。

请注意:本节和前一节都使用术语* finder *,并pass使用元路径查找器路径查找器进行区分。这两种类型的查找器非常相似,支持相似的协议,并且在导入过程中以相似的方式起作用,但是请务必记住它们之间的细微差别。特别地,元数据路径查找器在导入过程的开始进行操作,这是从sys.meta_path遍历开始的。

相比之下,从某种意义上说,路径条目查找器是基于路径的查找器的实现细节,实际上,如果要从sys.meta_path中删除基于路径的查找器,则不会调用任何路径条目查找器语义。

5.5.1. 路径条目查找器

基于路径的查找器负责查找和加载用字符串path entry指定位置的 Python 模块和软件包。大多数路径条目都在文件系统中命名位置,但不必局限于此。

作为元路径查找器,基于路径的查找器实现了先前介绍的find_spec()协议,但是它公开了可用于自定义如何从import path查找和加载模块的其他钩子。

基于路径的查找器sys.pathsys.path_hookssys.path_importer_cache使用了三个变量。也使用包对象上的__path__属性。这些提供了可以定制 import 机械的其他方式。

sys.path包含字符串列表,这些字符串提供了模块和软件包的搜索位置。它是passPYTHONPATH环境变量以及其他特定于安装和实现的默认值初始化的。 sys.path中的条目可以命名文件系统上的目录,zip 文件以及可能需要搜索模块(例如 URL 或数据库查询)的其他“位置”(请参见site模块)。 sys.path上只能有字符串和字节;所有其他数据类型将被忽略。字节条目的编码由单独的路径条目查找器确定。

基于路径的查找器元路径查找器,因此导入机制pass调用基于路径的查找器的find_spec()方法来开始import path搜索,如前所述。当给出find_spec()path参数时,它将是要遍历的字符串路径的列表-通常是该软件包中导入的软件包的__path__属性。如果path参数为None,则表示顶层导入,并使用sys.path

基于路径的查找器会遍历搜索路径中的每个条目,并针对其中的每个条目为路径条目寻找合适的路径查找器(PathEntryFinder)。由于这可能是一项昂贵的操作(例如,此搜索可能会有 stat()调用开销),因此基于路径的查找器会维护将路径条目 Map 到路径条目查找器的缓存。此缓存保留在sys.path_importer_cache中(尽管名称如此,该缓存实际上存储查找程序对象,而不是限于importer对象)。这样,仅需对一次path entry位置的路径查找器进行昂贵的搜索。用户代码可以自由地从sys.path_importer_cache中删除缓存条目,从而强制基于路径的查找器[3]再次执行路径条目搜索。

如果高速缓存中不存在路径条目,则基于路径的查找器将对sys.path_hooks中的每个可调用对象进行迭代。此列表中的每个路径 Importing 钩子都用一个参数(要搜索的路径条目)调用。此可调用对象可以返回可以处理路径条目的路径查找器,也可以引发ImportError。基于路径的查找器使用ImportError表示该钩子无法为该path entry找到路径查找器。忽略该异常,并且import path迭代 continue。钩子应该是字符串对象或字节对象;字节对象的编码取决于该钩子(例如,它可以是文件系统编码,UTF-8 或其他某种形式),如果该钩子无法解码该参数,则应加ImportError

如果sys.path_hooks迭代结束而没有返回路径查找器,则基于路径的查找器的find_spec()方法会将None存储在sys.path_importer_cache中(以指示该路径条目没有查找器)并返回None,指示此元路径查找器找不到模块。

如果sys.path_hooks上的路径进入钩子可调用对象之一返回了路径查找器 *,则使用以下协议向查找程序询问模块规格,然后在加载模块时使用。

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

5.5.2. 路径条目查找器协议

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

find_spec()接受两个参数:要导入的模块的标准名称,以及(可选)目标模块。 find_spec()返回模块的完整填充规格。此规范将始终设置“加载程序”(一个 exception)。

为了向导入设备指示该规范表示名称空间portion,路径条目查找器将规范上的“加载程序”设置为None,并将“ submodule_search_locations”设置为包含该部分的列表。

在版本 3.4 中进行了更改:find_spec()替换了find_loader()find_module(),现在都已弃用,但是如果未定义find_spec()则将使用它们。

较旧的路径条目查找器可以实现这两个不推荐使用的方法之一,而不是find_spec()。为了向后兼容,仍然尊重这些方法。但是,如果在路径条目查找器上实现了find_spec(),则遗留方法将被忽略。

find_loader()接受一个参数,即导入的模块的标准名称。 find_loader()返回一个 2Tuples,其中第一项是加载程序,第二项是命名空间portion。当第一项(即加载程序)为None时,这意味着尽管路径条目查找程序没有指定模块的加载程序,但它知道路径条目对命名模块的名称空间部分有所贡献。在几乎所有情况下,都会要求 Python 导入在文件系统上没有物理存在的名称空间包。当路径条目查找程序为加载程序返回None时,2Tuples 返回值的第二项必须是一个序列,尽管它可以为空。

如果find_loader()返回的非None加载器值,则忽略该部分,并从基于路径的查找器中返回加载器,从而终止对路径条目的搜索。

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

路径条目查找器上的find_module()方法已被弃用,因为它不允许路径条目查找器为名称空间包贡献部分。如果路径条目查找器上同时存在find_loader()find_module(),则导入系统将始终优先于find_module()调用find_loader()

5.6. 替换标准导入系统

替换整个导入系统的最可靠机制是删除sys.meta_path的默认内容,并使用自定义元路径钩子完全替换它们。

如果仅更改导入语句的行为而不影响访问导入系统的其他 API 是可以接受的,则替换内置的import()函数可能就足够了。也可以在模块级别采用此技术,以仅更改该模块内导入语句的行为。

为了有选择地阻止某些模块在元路径的早期从钩子中导入(而不是完全禁用标准导入系统),直接从find_spec()引发ModuleNotFoundError而不是返回None就足够了。后者表示应该 continue 执行元路径搜索,而引发异常会立即终止它。

5.7. 包装相对 import

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

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_的特殊注意事项

相对于 Python 的导入系统,main模块是一种特殊情况。如elsewhere所示,__main__模块在解释器启动时直接初始化,就像sysbuiltins一样。但是,与这两个模块不同的是,它并非严格符合内置模块的条件。这是因为初始化__main__的方式取决于调用解释器的标志和其他选项。

5.8.1. main.spec

根据main的初始化方式,可以适当设置__main__.__spec__或设置为None

当使用-m选项启动 Python 时,__spec__设置为相应模块或软件包的模块规格。当__main__模块作为执行目录,zipfile 或其他sys.path条目的一部分而加载时,也会填充__spec__

其余的情况中,__main__.__spec__设置为None,因为用于填充main的代码并不直接与可导入模块相对应:

  • interactive prompt

  • -c option

  • 从标准 Importing

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

请注意,在最后一种情况下,__main__.__spec__始终为None,即使*从技术上说,该文件可以直接作为模块导入。如果在main中需要有效的模块元数据,请使用-m开关。

还要注意,即使__main__对应于一个可导入模块,并相应地设置了__main__.__spec__,它们仍被视为* distinct *模块。这是由于以下事实:由if __name__ == "__main__":检查保护的块仅在使用模块填充__main__名称空间时执行,而在常规导入期间不执行。

5.9. 开放式问题

XXX 拥有图表真是太好了。

XXX *(import_machinery.rst)专门讨论模块和包属性的部分,也许在数据模型参考页面中的相关条目上有所扩展或取代了?

库手册中的 XXX runpy,pkgutil 等都应在顶部获得“另请参阅”链接,指向新的导入系统部分。

XXX 添加更多有关__main__初始化方式不同的解释?

XXX 添加有关__main__怪癖/陷阱的更多信息(即从 PEP 395复制)。

5.10. References

自 Python 成立以来,导入机制已经有了长足 Developing。原始的包装规格仍然可以阅读,尽管自编写该文档以来某些细节已更改。

sys.meta_path的原始规范为 PEP 302,其后扩展为 PEP 420

PEP 420为 Python 3.3 引入了namespace packages PEP 420还引入了find_loader()协议作为find_module()的替代。

PEP 366描述了为主要模块中的显式相对导入添加的__package__属性。

PEP 328引入了绝对和显式相对导入,并且最初针对语义提出的__name__ PEP 366finally将为__package__指定。

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

PEP 451在 spec 对象中添加了每个模块导入状态的封装。它还将装载机的大部分样板工作卸载回 import 机械上。这些更改允许在导入系统中弃用多个 API,还可以向查找器和加载器添加新方法。

Footnotes