5. 导入系统¶
一个模块中的Python代码通过导入的过程获得对另一个模块中的代码的访问。import
语句是调用导入机制的最常用方法,但它不是唯一的方法。诸如importlib.import_module()
和内置__import__()
之类的函数也可以用于调用导入机制。
import
语句组合两个操作;它搜索指定的模块,然后将该搜索的结果绑定到本地作用域中的名称。import
语句的搜索操作被定义为使用适当的参数调用__import__()
函数。__import__()
的返回值用于执行import
语句的名称绑定操作。有关该名称绑定操作的确切详细信息,请参见import
语句。
直接调用__import__()
只执行模块搜索,如果找到,则执行模块创建操作。虽然可能会发生某些副作用,例如导入父包以及更新各种缓存(包括sys.modules
),但只有import
语句会执行名称绑定操作。
当调用__import__()
作为import语句的一部分时,将调用标准的内置__import__()
。用于调用导入系统的其他机制(例如importlib.import_module()
)可以选择颠覆__import__()
并使用其自己的解决方案来实现导入语义。
首次导入模块时,Python会搜索模块,如果找到,它会创建一个模块对象[1],并初始化它。如果找不到指定的模块,则会引发ImportError
。当执行导入机制时,Python实现各种策略来搜索命名的模块。这些策略可以通过使用下面部分中描述的各种钩子来修改和扩展。
在版本3.3中更改:导入系统更新成完全实现 PEP 302的第二阶段。不再有任何隐式导入机制 - 完整导入系统通过sys.meta_path
暴露。此外,已实现原生命名空间包支持(参见 PEP 420)。
5.1. importlib
¶
importlib
模块提供了一个丰富的API,用于与导入系统进行交互。例如importlib.import_module()
提供了一个比内置的__import__()
更简单的API来调用导入机制。有关其他详细信息,请参阅importlib
库文档。
5.2. Packages¶
Python只有一种模块对象,所有的模块都是这种类型,不管这个模块是否是用Python,C,或者其他语言实现。为了帮助组织模块并提供命名层次结构,Python有一个概念:包。
您可以将程序包视为文件系统上的目录,将模块视为目录中的文件,但不要过于类比,因为程序包和模块不需要源自文件系统。出于本文档的目的,我们将使用目录和文件的这种方便的类比。像文件系统目录一样,包是以分层方式组织的,包本身可以包含子包以及常规模块。
重要的是要记住所有的包是模块,但不是所有的模块都是包。或者换句话说,包只是一种特殊的模块。具体来说,包含__path__
属性的任何模块都被视为包。
所有模块都有一个名称。子包名称与其父包名称之间用点分隔,类似于Python的标准属性访问语法。因此,您可能有一个名为sys
的模块和一个名为email
的软件包,其中包含一个名为email.mime
的子包,名为email.mime.text
的子包。
5.2.1. Regular packages¶
Python定义了两种类型的包,regular packages和namespace 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__.py
和parent/one/__init__.py
。随后导入parent.two
或parent.three
将执行parent/two/__init__.py
和parent/three/__init__.py
。
5.2.2. Namespace packages¶
命名空间包是各种portions的组合,其中每个部分都向父包提供子包。部分可以驻留在文件系统上的不同位置。部分也可以在zip文件,网络或Python在导入期间搜索的任何其他位置找到。命名空间包可以或可以不直接对应于文件系统上的对象;它们可以是没有具体表示的虚拟模块。
命名空间包不为其__path__
属性使用普通列表。它们改为使用自定义可迭代类型,如果其父包的路径(或对于顶级包的sys.path
),它将在该包中的下一次导入尝试时自动执行对包部分的新搜索变化。
使用命名空间包,没有parent/__init__.py
文件。实际上,在导入搜索期间可能会有多个parent
,其中每个目录由不同的部分提供。因此,parent/one
可能不在物理上位于parent/two
旁边。在这种情况下,Python将为顶层parent
包创建一个命名空间包,只要它或其中一个子包被导入。
另请参见命名空间包规范的 PEP 420。
5.3. Searching¶
要开始搜索,Python需要导入模块(或包,但为了讨论的目的,区别是无关紧要的)的fully qualified名称。此名称可能来自import
语句的各种参数,或者来自参数importlib.import_module()
或__import__()
函数的参数。
此名称将用于导入搜索的各个阶段,它可以是子模块的虚线路径,例如。foo.bar.baz
。在这种情况下,Python首先尝试导入foo
,然后foo.bar
,最后导入foo.bar.baz
。如果任何中间导入失败,则会引发ImportError
。
5.3.1. The module cache¶
在导入搜索期间检查的第一个位置是sys.modules
。此映射用作先前导入的所有模块的缓存,包括中间路径。So if foo.bar.baz
was previously imported, sys.modules
will contain entries for foo
, foo.bar
, and foo.bar.baz
. 每个键将具有作为其值的相应模块对象。
在导入期间,在sys.modules
中查找模块名称,如果存在,则关联的值是满足导入的模块,并且该过程完成。但是,如果值为None
,则会引发ImportError
。如果模块名称丢失,Python将继续搜索模块。
sys.modules
是可写的。删除键可能不会破坏关联的模块(因为其他模块可能保存对它的引用),但它将使指定模块的缓存条目无效,导致Python在下次导入时重新搜索指定的模块。该键也可以分配给None
,强制下次导入模块导致ImportError
。
注意,如果你保留对模块对象的引用,在sys.modules
中使其高速缓存条目无效,然后重新导入命名的模块,这两个模块对象将不相同。相比之下,importlib.reload()
将重用相同的模块对象,只需通过重新运行模块的代码重新初始化模块内容。
5.3.2. Finders and loaders¶
如果在sys.modules
中找不到指定的模块,那么将调用Python的导入协议来查找和加载模块。此协议由两个概念对象finders和loaders组成。finder的工作是确定它是否可以使用知道的任何策略找到命名的模块。实现这两个接口的对象称为importers - 当他们发现可以加载请求的模块时,它们返回自己。
Python包括许多默认查找器和导入器。第一个知道如何定位内部模块,第二个知道如何定位冻结模块。第三个默认查找器在import path中搜索模块。import path是可命名文件系统路径或zip文件的位置列表。它还可以扩展为搜索任何可定位资源,例如由URL标识的资源。
导入机制是可扩展的,因此可以添加新的查找器以扩展模块搜索的范围和范围。
查找器实际上不加载模块。如果他们能找到指定的模块,他们返回一个模块规范,模块的导入相关信息的封装,导入机制然后在加载模块时使用。
以下部分更详细地介绍查找器和装载程序的协议,包括如何创建和注册新协议以扩展导入机制。
在版本3.4中更改:在以前的Python版本中,finders直接返回loaders,而现在它们返回包含装载器的模块规格。装载机仍在导入期间使用,但责任较少。
5.3.3. Import hooks¶
进口机械设计为可扩展;其主要机制是导入挂钩。有两种类型的导入钩子:元钩子和导入路径钩子。
在导入处理开始时,在发生除了sys.modules
高速缓存查找之前的任何其他导入处理之前,调用元回路。这允许元钩子重写sys.path
处理,冻结模块,甚至内建模块。通过向sys.meta_path
添加新的finder对象来注册元钩子,如下所述。
在遇到它们相关的路径项时,将导入路径挂钩作为sys.path
(或package.__path__
)处理的一部分来调用。通过向sys.path_hooks
添加新的可调用项来注册导入路径挂接,如下所述。
5.3.4. The meta path¶
当在sys.modules
中找不到指定的模块时,Python会搜索sys.meta_path
,其中包含元路径查找器对象的列表。这些查找器被查询以查看他们是否知道如何处理命名的模块。元路径查找器必须实现一个名为find_spec()
的方法,它需要三个参数:名称,导入路径和(可选)目标模块。元路径查找器可以使用它想要的任何策略来确定它是否可以处理命名的模块。
如果元路径查找器知道如何处理命名模块,它返回一个spec对象。如果它无法处理指定的模块,则返回None
。如果sys.meta_path
处理在未返回规格的情况下到达其列表的末尾,则会引发ImportError
。任何其他引发的异常都会向上传播,中止导入过程。
使用两个或三个参数调用元路径查找器的find_spec()
方法。第一个是正在导入的模块的完全限定名称,例如foo.bar.baz
。第二个参数是用于模块搜索的路径条目。对于顶层模块,第二个参数是None
,但对于子模块或子包,第二个参数是父包的__path__
属性的值。如果无法访问适当的__path__
属性,则会引发ImportError
。第三个参数是将成为稍后加载目标的现有模块对象。导入系统仅在重新加载期间传递目标模块。
对于单个导入请求,元路径可以被遍历多次。For example, assuming none of the modules involved has already been cached, importing foo.bar.baz
will first perform a top level import, calling mpf.find_spec("foo", None, None)
on each meta path finder (mpf
). 在导入foo
之后,将通过遍历元路径第二次导入foo.bar
,调用mpf.find_spec(“foo.bar “, foo .__ path __, 无)
。一旦导入了foo.bar
,最终遍历将调用mpf.find_spec(“foo.bar.baz”, foo.bar .__ path__ , 无)
。
一些元路径查找器仅支持顶级导入。These importers will always return None
when anything other than None
is passed as the second argument.
Python的默认sys.meta_path
有三个元路径查找器,一个知道如何导入内建模块,一个知道如何导入冻结模块,一个知道如何从import path(即path based finder)。
在版本3.4中更改:元路径查找器的find_spec()
方法替换了find_module()
,现已弃用。While it will continue to work without change, the import machinery will try it only if the finder does not implement find_spec()
.
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:
if spec.submodule_search_locations is not None:
# namespace package
sys.modules[spec.name] = module
else:
# unsupported
raise ImportError
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]
请注意以下详细信息:
- If there is an existing module object with the given name in
sys.modules
, import will have already returned it.- The module will exist in
sys.modules
before the loader executes the module code. This is crucial because the module code may (directly or indirectly) import itself; adding it tosys.modules
beforehand prevents unbounded recursion in the worst case and multiple loading in the best.- If loading fails, the failing module – and only the failing module – gets removed from
sys.modules
. Any module already in thesys.modules
cache, and any module that was successfully loaded as a side-effect, must remain in the cache. This contrasts with reloading where even the failing module is left insys.modules
.- After the module is created but before execution, the import machinery sets the import-related module attributes (“_init_module_attrs” in the pseudo-code example above), as summarized in a later section.
- Module execution is the key moment of loading in which the module’s namespace gets populated. Execution is entirely delegated to the loader, which gets to decide what gets populated and how.
- The module created during loading and passed to exec_module() may not be the one returned at the end of import [2].
在版本3.4中更改:导入系统已接管装载机的样板设计职责。这些以前由importlib.abc.Loader.load_module()
方法执行。
5.4.1. Loaders¶
模块加载器提供加载的关键功能:模块执行。导入机制使用单个参数(要执行的模块对象)调用importlib.abc.Loader.exec_module()
方法。忽略从exec_module()
返回的任何值。
装载机必须满足以下要求:
- If the module is a Python module (as opposed to a built-in module or a dynamically loaded extension), the loader should execute the module’s code in the module’s global name space (
module.__dict__
).- If the loader cannot execute the module, it should raise an
ImportError
, although any other exception raised duringexec_module()
will be propagated.
在许多情况下,finder和loader可以是同一个对象;在这种情况下,find_spec()
方法将返回一个spec,加载器设置为self
。
模块加载器可以通过实现create_module()
方法在加载期间选择创建模块对象。它接受一个参数,模块规范,并返回在加载期间使用的新模块对象。create_module()
不需要在模块对象上设置任何属性。如果方法返回None
,导入机制将创建新模块本身。
版本3.4中的新功能: Loader的create_module()方法。
在版本3.4中更改: load_module()
方法替换为exec_module()
,导入机制假设所有样本负载加载。
为了与现有装载器兼容,导入机制将使用装载器的load_module()
方法(如果存在),并且装载器不实现exec_module()
。但是,load_module()
已被弃用,加载程序应该实现exec_module()
。
除执行模块之外,load_module()
方法必须实现上述所有样板加载功能。所有相同的约束适用,有一些额外的澄清:
- If there is an existing module object with the given name in
sys.modules
, the loader must use that existing module. (Otherwise,importlib.reload()
will not work correctly.) If the named module does not exist insys.modules
, the loader must create a new module object and add it tosys.modules
.- The module must exist in
sys.modules
before the loader executes the module code, to prevent unbounded recursion or multiple loading.- If loading fails, the loader must remove any modules it has inserted into
sys.modules
, but it must remove only the failing module(s), and only if the loader itself has loaded the module(s) explicitly.
在版本3.5中更改:在定义exec_module()
但create_module()
时,引发DeprecationWarning
从Python 3.6开始,在连接到ModuleSpec的加载器上不定义create_module()
将是一个错误。
5.4.2. Submodules¶
当子模块使用任何机制加载时。importlib
API,import
或import-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
模块中将名称绑定到foo
和bar
>>> 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. Module spec¶
导入机制在导入期间使用关于每个模块的各种信息,尤其是在加载之前。大多数信息对所有模块都是通用的。模块规范的目的是在每个模块的基础上封装这个导入相关的信息。
在导入期间使用规范允许状态在导入系统组件之间传送,例如,在创建模块spec的finder和执行它的loader之间。最重要的是,它允许导入机器执行加载的样板操作,而没有模块规范,加载器有责任。
有关模块规范可能包含的信息的更多细节,请参见ModuleSpec
。
版本3.4中的新功能。
5.4.5. module.__path__¶
根据定义,如果模块具有__path__
属性,则它是一个包,而不管其值。
包的__path__
属性在其子包的导入期间使用。在导入机制中,它的功能与sys.path
相同,即提供在导入期间搜索模块的位置列表。但是,__path__
通常比sys.path
更受限制。
__path__
必须是可迭代的字符串,但它可能为空。用于sys.path
的相同规则也适用于包的__path__
和sys.path_hooks
__path__
。
包的__init__.py
文件可以设置或更改包的__path__
属性,这通常是在 PEP 420。通过采用 PEP 420,命名空间包不再需要提供仅包含__path__
操作的__init__.py
文件码;导入机制会自动为命名空间包设置__path__
。
5.4.6. Module reprs¶
默认情况下,所有模块都具有可用的repr,但是根据上面设置的属性,在模块的spec中,可以更明确地控制模块对象的repr。
如果模块有一个spec(__spec__
),导入机制将尝试从它生成一个repr。如果失败或没有spec,导入系统将使用模块上可用的任何信息来创建默认repr。它将尝试使用module.__name__
,module.__file__
和module.__loader__
作为repr的输入,不见了。
以下是使用的确切规则:
- If the module has a
__spec__
attribute, the information in the spec is used to generate the repr. The “name”, “loader”, “origin”, and “has_location” attributes are consulted.- If the module has a
__file__
attribute, this is used as part of the module’s repr.- If the module has no
__file__
but does have a__loader__
that is notNone
, then the loader’s repr is used as part of the module’s repr.- Otherwise, just use the module’s
__name__
in the repr.
在版本3.4中已更改: loader.module_repr()
的使用已被弃用,导入机制现在使用模块规格来生成模块repr。
为了与Python 3.3向后兼容,在尝试上述两种方法之前,将通过调用加载器的module_repr()
方法(如果已定义)生成模块repr。但是,该方法已弃用。
5.5. The Path Based Finder¶
如前所述,Python附带了几个默认元路径查找器。其中一个称为path based finder(PathFinder
)搜索import path,其中包含path entries每个路径条目命名一个位置以搜索模块。
基于路径的finder本身不知道如何导入任何东西。相反,它遍历各个路径条目,将每个路径条目与知道如何处理该特定类型的路径的路径条目查找器相关联。
默认的路径条目查找器实现了用于在文件系统上查找模块的所有语义,处理特殊文件类型,例如Python源代码(.py
文件),Python字节代码(.pyc
文件)和共享库(例如.so
文件)。当标准库中的zipimport
模块支持时,默认路径条目查找器还会处理从zip文件加载所有这些文件类型(共享库除外)。
路径条目不必限于文件系统位置。它们可以引用URL,数据库查询或可以指定为字符串的任何其他位置。
基于路径的查找器提供额外的钩子和协议,以便您可以扩展和自定义可搜索路径条目的类型。例如,如果你想支持路径条目作为网络URL,你可以写一个钩子,实现HTTP语义在网络上找到模块。这个钩子(一个可调用的)将返回支持下面描述的协议的path entry finder,然后用于从web获得模块的加载器。
警告词:本节和前面两个使用术语finder,使用术语meta path finder和path entry finder区分它们>。这两种类型的查找器非常相似,支持类似的协议,并在导入过程中以类似的方式运行,但重要的是要记住,它们是微妙的不同。特别地,元路径查找器在导入过程的开始操作,因为sys.meta_path
遍历。
相反,路径条目查找器在某种意义上是基于路径的finder的实现细节,并且实际上,如果基于路径的finder将从sys.meta_path
中移除,则没有路径条目查找器语义将被调用。
5.5.1. Path entry finders¶
path based finder负责查找和加载Python模块和包,其位置由字符串path entry指定。大多数路径条目在文件系统中命名位置,但它们不必限于此。
作为元路径查找器,path based finder实现先前描述的find_spec()
协议,然而它暴露了可用于定制如何找到和加载模块的附加钩子从import path。
path based finder,sys.path
,sys.path_hooks
和sys.path_importer_cache
使用三个变量。还使用包对象上的__path__
属性。这些提供了进口机制可以定制的额外方式。
sys.path
包含提供模块和包的搜索位置的字符串列表。它从PYTHONPATH
环境变量和各种其他安装和实现特定的默认值初始化。sys.path
中的条目可以命名文件系统上的目录,zip文件以及应该搜索模块的其他“位置”(参见site
模块)作为URL或数据库查询。只有字符串和字节应出现在sys.path
;所有其他数据类型将被忽略。字节条目的编码由各个path entry finders确定。
The path based finder is a meta path finder, so the import machinery begins the import path search by calling the path based finder’s find_spec()
method as described previously. 当给定find_spec()
的path
参数时,它将是要遍历的字符串路径的列表 - 通常是包的__path__
属性在该包中导入。如果path
参数为None
,则表示使用顶层导入,而使用sys.path
。
基于路径的查找器遍历搜索路径中的每个条目,并且对于每个条目,为路径条目寻找适当的path entry finder(PathEntryFinder
)。因为这可能是昂贵的操作(例如,可能有stat()用于此搜索的调用开销),基于路径的查找器维护到路径条目查找器的高速缓存映射路径条目。此高速缓存保存在sys.path_importer_cache
(尽管名称中,此高速缓存实际上存储finder对象,而不是仅限于importer对象)。以这种方式,对特定path entry位置path entry finder的昂贵搜索仅需要进行一次。用户代码可以从sys.path_importer_cache
中自由删除高速缓存条目,强制基于路径的查找器再次执行路径条目搜索[3]。
如果路径条目不存在于高速缓存中,则基于路径的查找器遍历sys.path_hooks
中的每个可调用项。此列表中的每个path entry hooks都使用单个参数调用,即要搜索的路径条目。此可调用可以返回可以处理路径条目的path entry finder,或者可以引发ImportError
。基于路径的查找器使用ImportError
来表示钩子不能为path entry找到path entry finder。异常将被忽略,并且import path迭代继续。钩子应该期望一个字符串或字节对象;字节对象的编码直到挂钩。它可能是一个文件系统编码,UTF-8或其他),如果钩子不能解码参数,它应该引发ImportError
。
If sys.path_hooks
iteration ends with no path entry finder being returned, then the path based finder’s find_spec()
method will store None
in sys.path_importer_cache
(to indicate that there is no finder for this path entry) and return None
, indicating that this meta path finder could not find the module.
If a path entry finder is returned by one of the path entry hook callables on sys.path_hooks
, then the following protocol is used to ask the finder for a module spec, which is then used when loading the module.
当前工作目录(由空字符串表示)与sys.path
上的其他条目稍有不同。首先,如果发现当前工作目录不存在,则没有值存储在sys.path_importer_cache
中。其次,为每个模块查找新的当前工作目录的值。第三,用于sys.path_importer_cache
并由importlib.machinery.PathFinder.find_spec()
返回的路径将是实际的当前工作目录,而不是空字符串。
5.5.2. Path entry finder protocol¶
为了支持导入模块和初始化包以及为命名空间包提供部分,路径入口查找器必须实现find_spec()
方法。
find_spec()
有两个参数,即正在导入的模块的完全限定名称和(可选)目标模块。find_spec()
返回模块的完全填充规范。此规范将始终具有“loader”设置(有一个例外)。
向导入机制指示规范表示命名空间portion。路径入口查找器将规范上的“loader”设置为None
和“submodule_search_locations”到包含该部分的列表。
在版本3.4中更改: find_spec()
替换了find_loader()
和find_module()
已弃用,但会在未定义find_spec()
的情况下使用。
较老的路径入口查找器可以实现这两个不推荐使用的方法之一,而不是find_spec()
。该方法仍然是为了向后兼容性的考虑。但是,如果在路径条目查找器上实现find_spec()
,则会忽略旧方法。
find_loader()
接受一个参数,即正在导入的模块的完全限定名称。find_loader()
返回一个2元组,其中第一个项是加载器,第二个项是名称空间portion。当第一项(即,加载器)为None
,这意味着虽然路径入口查找器没有用于命名模块的加载器,但它知道路径入口有助于命名模块的命名空间部分。这将几乎总是这样的情况,其中要求Python导入在文件系统上没有物理存在的命名空间包。当路径入口查找器为加载器返回None
时,2元组返回值的第二项必须是序列,尽管它可以为空。
如果find_loader()
返回非None
加载程序值,则会忽略该部分,并从基于路径的查找程序返回加载程序,从而终止通过路径条目的搜索。
为了向后兼容导入协议的其他实现,许多路径入口查找器也支持元路径查找器支持的相同的,传统的find_module()
方法。然而路径条目查找器find_module()
方法从来不用path
参数调用(他们希望记录从初始调用到路径钩子的适当的路径信息)。
路径条目查找器上的find_module()
方法已被弃用,因为它不允许路径条目查找器对命名空间包提供部分。If both find_loader()
and find_module()
exist on a path entry finder, the import system will always call find_loader()
in preference to find_module()
.
5.6. Replacing the standard import system¶
用于替换整个导入系统的最可靠的机制是删除sys.meta_path
的默认内容,将它们完全替换为自定义元路径钩子。
如果只接受在不影响访问导入系统的其他API的情况下更改import语句的行为,那么替换内置__import__()
函数可能就足够了。这种技术也可以在模块级别被采用以仅改变该模块内的import语句的行为。
To selectively prevent import of some modules from a hook early on the meta path (rather than disabling the standard import system entirely), it is sufficient to raise ImportError
directly from find_spec()
instead of returning None
. 后者指示元路径搜索应该继续,而提高异常立即终止它。
5.7. Special considerations for __main__¶
__main__
模块是相对于Python导入系统的特殊情况。如elsewhere所述,__main__
模块在解释器启动时直接初始化,就像sys
和builtins
。然而,不像这两个,它不严格限定为内建模块。这是因为__main__
的初始化方式取决于调用解释器的标志和其他选项。
5.7.1. __main__.__spec__¶
根据__main__
如何初始化,__main__.__spec__
设置为适当或None
。
当Python以-m
选项启动时,__spec__
设置为相应模块或包的模块规范。当__main__
模块作为执行目录,zipfile或其他sys.path
条目的一部分加载时,还会填充__spec__
In the remaining cases __main__.__spec__
is set to None
, as the code used to populate the __main__
does not correspond directly with an importable module:
- 交互式提示
- -c开关
- 从stdin运行
- 直接从源或字节码文件运行
请注意,在最后一种情况下,__main__.__spec__
始终为None
,即使该文件可以直接作为模块直接导入。如果在__main__
中需要有效的模块元数据,请使用-m
开关。
还要注意,即使__main__
对应于可导入模块,并且相应地设置了__main__.__spec__
,它们仍被视为distinct模块。这是因为如果 __ name __ == “__ main __”保护的块: / t0>检查仅在模块用于填充
__main__
命名空间时执行,而不是在正常导入期间执行。
5.8. Open issues¶
XXX这将是非常好的有一个图。
XXX *(import_machinery.rst)一个专门介绍模块和包的属性的部分,或者扩展或替换数据模型参考页中的相关条目?
XXX runpy,pkgutil等在库手册中应该都得到“See Also”链接在顶部指向新的导入系统部分。
XXX添加关于__main__
初始化的不同方式的更多说明?
XXX在__main__
上添加更多信息quirks / pitfalls(即从 PEP 395复制)。
5.9. References¶
从Python的早期开始,导入机制已经发生了很大的变化。包的原始规范仍可供阅读,但自该文档编写以来一些细节已更改。
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 introduced absolute and explicit relative imports and initially proposed __name__
for semantics PEP 366 would eventually specify for __package__
.
PEP 338将执行模块定义为脚本。
PEP 451在spec对象中添加了每个模块导入状态的封装。它还将装载机的大部分样板责任卸载回进口机械。这些更改允许在导入系统中弃用多个API,并向查找器和装载器添加新方法。
脚注
[1] | 请参见types.ModuleType 。 |
[2] | importlib实现避免直接使用返回值。相反,它通过在sys.modules 中查找模块名称来获取模块对象。其间接效果是,导入的模块可以在sys.modules 中替换自身。这是实现特定的行为,不能保证在其他Python实现中工作。 |
[3] | 在旧代码中,可以在sys.path_importer_cache 中找到imp.NullImporter 的实例。建议将代码更改为使用None 。有关详细信息,请参阅Porting Python code。 |