本人翻译,转载请注明出处
5. 导入系统
一个模块中的Python代码通过导入的过程可以访问另一个模块的代码。import
语句是调用导入机制的最常见的方式,但不是唯一的方式。例如importlib.import_module()
和内建函数__import__()
也可以用来调用导入机制。
import
语句结合了两个操作;搜索指定模块,然后在本地作用域把搜索的结果和名字绑定。import
语句的搜索的操作被定义为一次__import__()
函数的调用,传入一些合适的参数。__import__()
的返回值被用来执行import
语句的命名绑定的操作。命名绑定的确切的细节参见import
语句。
__import__()
的直接调用仅执行模块的搜索,如果有结果,还有模块的创建操作。当( 执行__import__()
函数时)某些副作用可能会发生,例如导入父模块,还有更新很多缓存(包括sys.modules
),只有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. 包
Python只有一种模块对象的类型,所有的模块都是这种类型,无论这个模块是由Python,C还是其他语言实现的。为帮助组织模块提供命名继承,Python有包的概念。
你可以把包想成是一个文件系统的目录而模块是目录下的文件,不过不要对这个比喻太咬文嚼字因为包和模块并不需要来自文件系统。就本文档的目的,我们将使用这种目录和文件的方便的比喻。类似文件系统的目录,包依层级组织,并且包它们本身也可能包含子包,或者是常规模块。
重要的是记住所有的包都是模块,但不是所有的模块都是包。或者换种说法,包只是一种特别的模块。特别的,任何含有__path__
属性的模块都被认为是包。
所有模块都有一个名字。子包名字和他们的父包的名字用一个点分隔,类似于Python的标准属性的访问语法。因此你或许有一个名为sys
的模块和一个名为email
的包,其中包含一个名为email.mime
的子包和这个子包中的一个名为email.mime.text
的模块。
5.2.1. 常规包
Python定义了两种类型的包,常规包和命名空间包。常规包是传统的包,他们存在于Python 3.2及更早的版本中。一个常规包通常实现为一个包含一个__init__.py
文件的目录。 当导入常规包时,这个__init__.py
文件被隐式执行,它定义的对象被绑定到包命名空间中的名称。 __init__.py
文件可以包含与任何其他模块可以包含的Python代码相同的Python代码,Python会在导入时为模块添加一些其他属性。
例如,以下文件系统布局定义具有三个子包的顶级父包:
导入parent.one
将隐式执行parent/__init__.py
和parent/one/__init__.py
。 后续导入的parent.two
或parent.three
将分别执行parent/two/__init__.py
和parent/three/__init__.py
。
5.2.2. 命名空间包
命名空间包由各种组分组成,其中每个组分向父包提供子包。组分可以驻留在文件系统上的不同位置。组分也可以在zip文件,网络或Python在导入期间搜索的任何其他位置找到。命名空间包可以或可以不直接对应于文件系统上的对象;它们可以是没有具体代表的虚拟模块。
命名空间包的__path__
属性不使用普通的列表。它们改为使用自定义可迭代类型,如果其父包(或顶级包的sys.path
)的路径发生更改,则会在该包中的下一次导入尝试时自动执行对包组分的新搜索。
使用命名空间包,没有parent/__init__.py
文件。实际上,在导入搜索期间可能会找到多个parent
目录,其中每个目录由不同的组分提供。因此,parent/one
可能不在物理上位于parent/two
旁边。在这种情况下,每当导入顶层父包或其中一个子包时,Python将为顶级父包创建命名空间包。
有关命名空间包规范,请参见PEP 420。
5.3. 搜索
要开始搜索,Python需要待导入模块(或包,但为了讨论的目的,差别是无关紧要的)的完全限定名。此名称可能来自import
语句的各种参数,或来自importlib.import_module()
或__import__()
函数的参数。
此名称将用于导入搜索的各个阶段,它可以是子模块的点分隔路径,例如foo.bar.baz
。 在这种情况下,Python首先尝试导入foo
,然后是foo.bar
,最后是foo.bar.baz
。 如果任何中间导入失败,则会引发ImportError
。
5.3.1. 模块缓存
在导入搜索期间检查的第一个位置是sys.modules
。此映射充当先前导入的所有模块的缓存,包括中间路径。 因此,如果以前导入foo.bar.baz
,sys.modules
将包含foo
,foo.bar
和foo.bar.baz
的条目。 每个键将具有作为其值的相应模块对象。
在导入期间,模块名称在sys.modules
中被查找,如果存在,则关联的值是满足导入的模块,并且导入过程完成。 但是,如果值为None
,则会引发ImportError
。 如果模块名称丢失,Python将继续搜索模块。
sys.modules
是可写的。 删除键可能不会破坏关联的模块(因为其他模块可能保存对它的引用),但它将使指定模块的缓存条目无效,导致Python在下次导入时重新搜索指定的模块。 该键也可以分配为None
,强制下一次导入模块导致ImportError
。
注意,如果你保留对模块对象的引用,使其在sys.modules
中的缓存条目无效,然后重新导入指定模块,两个模块对象将不会相同。 相比之下,importlib.reload()
将重用相同的模块对象,并且通过重新运行模块的代码来重新初始化模块内容。
5.3.2. 查找器和加载器
如果在sys.modules
中找不到指定模块,那么将调用Python的导入协议来查找和加载模块。该协议由两个概念对象查找器和加载器组成。查找器的工作是确定是否可以用它已知的无论什么策略找到指定模块。实现这两个接口的对象被称为导入器 - 当他们发现他们可以加载请求的模块时,它们返回自己。
Python包括许多默认查找器和导入器。 第一个知道如何定位内置模块,第二个知道如何定位冻结模块。 第三个默认查找器在导入路径中搜索模块。 导入路径是可以命名文件系统路径或zip文件的位置列表。 它还可以扩展为搜索任何可定位资源,例如由URL标识的资源。
导入机制是可扩展的,因此可以添加新的查找器以扩展模块搜索的范围和范围。
查找器实际上不加载模块。 如果他们可以找到指定模块,他们返回一个模块规范,模块的导入相关信息的封装,导入机制然后在加载模块时使用。
以下部分更详细地介绍查找器和装载程序的协议,包括如何创建和注册新协议以扩展导入机制。
在版本3.4中更改: 在以前的Python版本中,查找器直接返回加载器,而现在它们返回包含装载器的模块规格。 装载机仍在导入期间使用,但责任较少。
5.3.3. 导入钩子
导入机制被设计为可拓展的;这个的主要机制是导入钩子。有两种类型的导入钩子:元钩子和导入路径钩子。
元钩子在导入进行前被调用,在任何其他导入过程开始之前,除了sys.modules
缓存查找。这运行元钩子覆盖sys.path
处理,冻结模块,甚至内置模块。通过向sys.meta_path
添加新的查找器对象来注册元钩子,如下所述。
导入路径钩子作为sys.path
(或package.__ path__
)处理的一部分,在遇到它们相关的路径项的点被调用。 通过向sys.path_hooks
添加新的可调用项来注册导入路径挂接,如下所述。
5.3.4. 元路径
当在sys.modules
中无法找到指定模块,Python接下来搜索sys.meta_path
, which contains a list of meta path finder objects. These finders are queried in order to see if they know how to handle the named module. Meta path finders must implement a method called find_spec()
which takes three arguments: a name, an import path, and (optionally) a target module. The meta path finder can use any strategy it wants to determine whether it can handle the named module or not.
If the meta path finder knows how to handle the named module, it returns a spec object. If it cannot handle the named module, it returns None
. If sys.meta_path
processing reaches the end of its list without returning a spec, then an ImportError
is raised. Any other exceptions raised are simply propagated up, aborting the import process.
The find_spec()
method of meta path finders is called with two or three arguments. The first is the fully qualified name of the module being imported, for example foo.bar.baz
. The second argument is the path entries to use for the module search. For top-level modules, the second argument is None, but for submodules or subpackages, the second argument is the value of the parent package’s __path__
attribute. If the appropriate __path__
attribute cannot be accessed, an ImportError
is raised. The third argument is an existing module object that will be the target of loading later. The import system passes in a target module only during reload.
The meta path may be traversed multiple times for a single import request. 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
). After foo
has been imported, foo.bar
will be imported by traversing the meta path a second time, calling mpf.find_spec("foo.bar", foo.__path__, None)
. Once foo.bar
has been imported, the final traversal will call mpf.find_spec("foo.bar.baz", foo.bar.__path__, None)
.
Some meta path finders only support top level imports. These importers will always return None
when anything other than None
is passed as the second argument.
Python’s default sys.meta_path
has three meta path finders, one that knows how to import built-in modules, one that knows how to import frozen modules, and one that knows how to import modules from an import path (i.e. the path based finder).
在版本3.4中更改: The find_spec()
method of meta path finders replaced find_module()
, which is now deprecated. While it will continue to work without change, the import machinery will try it only if the finder does not implement find_spec()
.