import system
一个 module 内的 Python 代码通过 importing 操作就能够访问另一个模块内的代码。import 语句是发起调用导入机制的最常用方式,但不是唯一的方式。importlib.import_module() 以及内置的 __import__() 等函数也可以被用来发起调用导入机制。
import 语句结合了两个操作:它先搜索指定名称的模块,然后将搜索结果绑定到当前作用域中的名称。 import 语句的搜索操作定义为对 __import__() 函数的调用并带有适当的参数。 __import__() 的返回值会被用于执行 import 语句的名称绑定操作。
对 __import__() 的直接调用将仅执行模块搜索以及在找到时的模块创建操作。 不过也可能产生某些副作用,例如导入父包和更新各种缓存 (包括 sys.modules),只有 import 语句会执行名称绑定操作。
当 import 语句被执行时,标准的内置 __import__() 函数会被调用。 其他发起调用导入系统的机制 (例如 importlib.import_module()) 可能会选择绕过 __import__() 并使用它们自己的解决方案来实现导入机制。
当一个模块首次被导入时,Python 会搜索该模块,如果找到就创建一个 module 对象并初始化它。 如果指定名称的模块未找到,则会引发 ModuleNotFoundError。 当发起调用导入机制时,Python 会实现多种策略来搜索指定名称的模块。
importlib
importlib 模块提供了一个丰富的 API 用来与导入系统进行交互。 例如 importlib.import_module() 提供了相比内置的 __import__() 更推荐、更简单的 API 用来发起调用导入机制。但这里并不涉及。
Packages
Python 只有一种 module 对象类型,所有模块都属于该类型,无论模块是用 Python、C 还是别的语言实现。 为了帮助组织模块并提供名称层次结构,Python 还引入了 Packages 的概念。
可以把包看成是文件系统中的目录,并把模块看成是目录中的文件,但请不要对这个类似做过于字面的理解,因为包和模块不是必须来自于文件系统。 与文件系统一样,包通过层次结构进行组织,在包之内除了一般的模块,还可以有子包。
要注意的一个重点概念是所有包都是模块,但并非所有模块都是包。 或者换句话说,包只是一种特殊的模块。 特别地,任何具有 __path__ 属性的模块都会被当作是包。
所有模块都有自己的名字。 子包名与其父包名以点号分隔,与 Python 的标准属性访问语法一致。 例如你可能看到一个名为 sys 的模块,以及一个名为 email 的包,这个包又有一个名为 email.mine 的子包和该子包中的名为 email.mine.text 的子包。
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。
Namespace packages
命名空间包是由多个 portion 构成的,每个部分为父包增加一个子包。 各个部分可能处于文件系统的不同位置。 部分也可能处于 zip 文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。 命名空间包并不一定会直接对应到文件系统中的对象,它们有可能是无实体表示的虚拟模块。
命名空间包的 __path__ 属性不使用普通的列表。 而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的 sys.path) 发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。
命名空间包没有 parent/__init__.py 文件。 实际上,在导入搜索期间可能找到多个 parent 目录,每个都由不同的部分所提供。 因此 parent/one 的物理位置不一定与 parent/two 相邻。 在这种情况下,Python 将为顶级的 parent 包创建一个命名空间包,无论是它本身还是它的某个子包被导入。
importing
令一个模块中的 Python 代码能为另一个模块中的 Python 代码所使用的过程。
package
一种可包含子模块或递归地包含子包的 Python module。从技术上说,包是带有 __path__ 属性的 Python 模块。
module
此对象是 Python 代码的一种组织单位。各模块具有独立的命名空间,可包含任意 Python 对象。模块可通过 importing 操作被加载到 Python 中
portion
构成一个命名空间包的单个目录内文件集合(也可能存放于一个 zip 文件内)。
import statement
import_stmt ::= "import" module ["as" identifier] ("," module ["as" identifier])*
| "from" relative_module "import" identifier ["as" identifier]
("," identifier ["as" identifier])*
| "from" relative_module "import" "(" identifier ["as" identifier]
("," identifier ["as" identifier])* [","] ")"
| "from" module "import" "*"
module ::= (identifier ".")* identifier
relative_module ::= "."* module | "."+
基本的 import 语句(不带 from 子句)会分两步执行:
1. 查找一个模块,如果有必要还会加载并初始化模块。
2. 在局部命名空间中为 import 语句发生位置所处的作用域定义一个或多个名称。
当语句包含多个子句(由逗号分隔)时这两个步骤将对每个子句分别执行,如同这些子句被分成独立的 import 语句一样。
第一个步骤即查找和加载模块的详情 import system 中有更详细的描述,其中也描述了可被导入的多种类型的包和模块,以及可用于定制导入系统的所有对象。 请注意这一步如果失败,则可能说明模块无法找到,或者是在初始化模块,包括执行模块代码期间发生了错误。
如果成功获取到请求的模块,则可以通过以下三种方式一之在局部命名空间中使用它:
1. 如果模块名称之后带有 as,则跟在 as 之后的名称将直接绑定到所导入的模块。
2. 如果没有指定其他名称,且被导入的模块为最高层级模块,则模块的名称将被绑定到局部命名空间作为对所导入模块的引用。
3. 如果被导入的模块不是最高层级模块,则包含该模块的最高层级包的名称将被绑定到局部命名空间作为对该最高层级包的引用。 所导入的模块必须使用其完整限定名称来访问而不能直接访问。
from 形式使用的过程略微繁复一些:
1. 查找 from 子句中指定的模块,如有必要还会加载并初始化模块;
2. 对于 import 子句中指定的每个标识符:
a. 检查被导入模块是否有该名称的属性
b. 如果没有,尝试导入具有该名称的子模块,然后再次检查被导入模块是否有该属性
c. 如果未找到该属性,则引发 ImportError。
d. 否则的话,将对该值的引用存入局部命名空间,如果有 as 子句则使用其指定的名称,否则使用该属性的名称
示例:
import foo # foo imported and bound locally
import foo.bar.baz # foo.bar.baz imported, foo bound locally
import foo.bar.baz as fbb # foo.bar.baz imported and bound as fbb
from foo.bar import baz # foo.bar.baz imported and bound as baz
from foo import attr # foo imported and foo.attr bound as attr
如果标识符列表改为一个星号(" * "),则在模块中定义的全部公有名称都将按 import 语句所在的作用域被绑定到局部命名空间。
一个模块所定义的公有名称是由在模块的命名空间中检测一个名为 __all__ 的变量来确定的;如果有定义,它必须是一个字符串列表,其中的项为该模块所定义或导入的名称。 在 __all__ 中所给出的名称都会被视为公有并且应当存在。 如果 __all__ 没有被定义,则公有名称的集合将包含在模块的命名空间中找到的所有不以下划线字符 (" _ ")打头的名称(言外之意为不包含私有变量)。 __all__ 应当包括整个公有 API。 它的目标是避免意外地导出不属于 API 的一部分的项(例如在模块内部被导入和使用的库模块)。
通配符形式的导入 from module import * 仅在模块层级上被允许。 尝试在类或函数定义中使用它将引发 SyntaxError。
当指定要导入哪个模块时,不必指定模块的绝对名称。 当一个模块或包被包含在另一个包之中时,可以在同一个最高层级包中进行相对导入,而不必提及包名称。 通过在 from 之后指定的模块或包中使用前缀点号,可以在不指定确切名称的情况下指明在当前包层级结构中要上溯多少级。 一个前缀点号表示是执行导入的模块所在的当前包,两个点号表示上溯一个包层级。 三个点号表示上溯两级,依此类推。 因此如果你执行 from . import mod 时所处位置为 pkg 包内的一个模块,则最终你将导入 pkg.mod。 如果你执行 from ..subpkg2 import mod 时所处位置为 pkg.subpkg1 则你将导入pkg.subpkg2.mod。
Package Relative Imports
相对导入使用前缀点号。 一个前缀点号表示相对导入从当前包开始。 两个或更多前缀点号表示对当前包的上级包的相对导入,第一个点号之后的每个点号代表一级。 例如,给定以下的包布局结构:
package/ __init__.py subpackage1/ __init__.py moduleX.py moduleY.py subpackage2/ __init__.py moduleZ.py moduleA.py
不论是在 subpackage1/moduleX.py 还是 subpackage1/__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 不是一个有效的表达式。
参考资料
1. Python 官方文档:https://docs.python.org/zh-cn/3.7/