|
- 一、概述
- 二、导入语句
- 三、模块
- 1、模块名
- 2、模块属性
- 3、可导出的公有属性
- 4、直接执行
- 四、包
- 1、包名
- 2、包属性
- 3、可导出的公有属性
- 4、其他
- 五、导入原理
- 1、导入依赖
- 2、导入过程
- 3、更多细节
- 4、sys.path
- 六、重新加载
- 七、相对导入
一、概述
模块(module)和 包(package)是Python用于组织大型程序的利器。
模块 是一个由 变量、函数、类 等基本元素组成的功能单元,设计良好的模块通常是高内聚、低耦合、可复用、易维护的。包 是管理模块的容器,它具有 可嵌套性:一个包可以包含模块和其他包。从文件系统的视角来看,包就是目录,模块就是文件。
从本质上讲,一个模块就是一个独立的名字空间(namespace),单纯的多个模块只能构成扁平结构的名字空间集;而包的可嵌套性,使得多个模块可以呈现出多层次结构的名字空间树。
二、导入语句
如果要在一个模块A中使用另一个模块B(即访问模块B的属性),则必须首先 导入 模块B。此时模块A称为导入模块(即importer),而模块B称为被导入模块(即importee)。
导入语句(import statement)有两种风格:import 和from import。对模块的导入同时支持这两种风格。
1、基本语法
1)导入模块module(重命名为name)
import module [as name]
>>> import sys
>>> sys.version
'2.7.3 (default, Apr 10 2013, 05:46:21) \n[GCC 4.6.3]'
>>> import sys as s
>>> s.version
'2.7.3 (default, Apr 10 2013, 05:46:21) \n[GCC 4.6.3]'
2)导入模块module1(重命名为name1),模块module2(重命名为name2),等等
import module1 [as name1], module2 [as name2], ...
>>> import sys, os
>>> sys.platform, os.name
('linux2', 'posix')
>>> import sys as s, os as o
>>> (s.platform, o.name)
('linux2', 'posix')
3)从模块module中导入属性attribute(重命名为name)
from module import attribute [as name]
>>> from sys import executable
>>> executable
'/usr/bin/python'
>>> from sys import executable as exe
>>> exe
'/usr/bin/python'
4)从模块module中导入属性attribute1(重命名为name1),属性attribute2(重命名为name2),等等
from module import attribute1 [as name1], attribute2 [as name2], ...
>>> from sys import platform, executable
>>> platform, executable
('linux2', '/usr/bin/python')
>>> from sys import platform as plf, executable as exe
>>> plf, exe
('linux2', '/usr/bin/python')
5)从模块module中导入属性attribute1(重命名为name1),属性attribute2(重命名为name2),等等
from module import (attribute1 [as name1], attribute2 [as name2], ...)
>>> from sys import (platform, executable)
>>> platform, executable
('linux2', '/usr/bin/python')
>>> from sys import (platform as plf, executable as exe)
>>> plf, exe
('linux2', '/usr/bin/python')
6)从模块module中导入所有属性
from module import *
>>> from sys import *
>>> platform, executable
('linux2', '/usr/bin/python')
2、推荐风格
以下是在Python程序中推荐使用的导入语句:
- import module [as name](导入单个模块)
- from module import attribute [as name](导入单个属性)
- from module import attribute1 [as name1], attribute2 [as name2], ...(导入较少属性时,单行书写)
- from module import (attribute1 [as name1], attribute2 [as name2], ...)(导入较多属性时,分行书写)
应当尽量避免使用的导入语句是:
- import module1 [as name1], module2 [as name2], ...
它会降低代码的可读性,应该用多个import module [as name]语句代替。
- from module import *
它会让importer的名字空间变得不可控(很可能一团糟)。
三、模块
1、模块名
一个 模块 就是一个Python源码文件。如果文件名为mod.py,那么模块名就是mod。
模块的导入和使用都是借助模块名来完成的,模块名的命名规则与变量名相同。
2、模块属性
模块属性 是指在模块文件的全局作用域内,或者在模块外部(被其他模块导入后)可以访问的所有对象名字的集合。这些对象名字构成了模块的名字空间,这个名字空间其实就是全局名字空间(参考 名字空间与作用域)。
模块的属性由两部分组成:固有属性 和 新增属性。可以通过 M.__dict__ 或 dir(M) 来查看模块M的属性。
1)固有属性
固有属性 是Python为模块默认配置的属性。
例如,新建一个空文件mod.py:
$ touch mod.py
$ python
...
>>> import mod # 导入模块mod
>>> mod.__dict__ # 模块mod的属性全貌
{'__builtins__': {...}, '__name__': 'mod', '__file__': 'mod.pyc', '__doc__': None, '__package__': None}
>>> dir(mod) # 只查看属性名
['__builtins__', '__doc__', '__file__', '__name__', '__package__']
上述示例中,空模块mod的所有属性都是固有属性,包括:
- __builtins__ 内建名字空间(参考 名字空间)
- __file__ 文件名(对于被导入的模块,文件名为绝对路径格式;对于直接执行的模块,文件名为相对路径格式)
- __name__ 模块名(对于被导入的模块,模块名为去掉“路径前缀”和“.pyc后缀”后的文件名,即os.path.splitext(os.path.basename(__file__))[0];对于直接执行的模块,模块名为__main__)
- __doc__ 文档字符串(即模块中在所有语句之前第一个未赋值的字符串)
- __package__ 包名(主要用于相对导入,请参考 PEP 366)
2)新增属性
新增属性 是指在模块文件的顶层(top-level),由赋值语句(如import、=、def和class)创建的属性。
例如,修改文件mod.py为:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''this is a test module'''
import sys
debug = True
_static = ''
class test_class(object):
def say(self): pass
def test_func():
var = 0
再次查看模块mod的属性:
>>> import mod
>>> dir(mod)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '_static', 'debug', 'sys', 'test_class', 'test_func']
对比上一小节可知,除开固有属性外的其他属性都是新增属性,包括:
- sys(由“import”创建)
- debug和_static(均由“=”创建)
- test_class(由“class”创建)
- test_func(由“def”创建)
这些属性的共同点是:它们都在模块文件的顶层创建。相比之下,类方法say(在类test_class内部创建)和局部变量var(在函数test_func内部创建)都不在顶层,因此不在新增属性之列。(作为一个例子,'''this is a test module'''就是模块mod的文档字符串,即mod.__doc__的值)
3、可导出的公有属性
在 『导入语句』 中描述的基本语法可以归纳为三类:
- import module 在导入模块(importer)中,可以通过module.*的形式访问模块module中的所有属性
- from module import attribute 只能通过名字attribute访问模块module中的指定属性module.attribute
- from module import * 可以直接通过属性名访问模块module中的所有 公有属性
换句话说,模块的 公有属性 就是那些可以通过from module import *被导出给其他模块直接使用的属性。
模块的公有属性有以下特点:
- 可以在模块中定义一个特殊列表__all__,其中包含所有可导出的公有属性的字符串名称,从而实现对公有属性的定制
- 如果没有定义__all__,那么默认所有不以下划线“_”开头的属性都是可导出的公有属性
以 『新增属性』 中的mod.py为例,没有定义__all__的公有属性:
>>> dir() # 导入模块mod前的名字空间
['__builtins__', '__doc__', '__name__', '__package__']
>>> from mod import * # 导入模块mod中的所有公有属性
>>> dir() # 导入模块mod后的名字空间
['__builtins__', '__doc__', '__name__', '__package__', 'debug', 'sys', 'test_class', 'test_func']
对比导入模块mod前后的情况可知,模块mod中的sys、debug、test_class和test_func都属于公有属性,因为它们的名字不以“_”开头;而其他以“_”开头的属性(包括所有“固有属性”,以及“新增属性”中的_static)都不在公有属性之列。
如果在模块文件mod.py中顶层的任何位置,增加定义一个特殊列表__all__ = ['sys', '_static', 'test_func'](此时__all__也是模块属性),那么此时的公有属性:
>>> dir() # 导入模块mod前的名字空间
['__builtins__', '__doc__', '__name__', '__package__']
>>> from mod import * # 导入模块mod中的所有公有属性
>>> dir() # 导入模块mod后的名字空间
['__builtins__', '__doc__', '__name__', '__package__', '_static', 'sys', 'test_func']
可以看出,只有在__all__中指定的属性才是公有属性。
4、直接执行
模块可以在命令行下被直接执行,以模块mod(对应文件mod.py)为例:
1)以脚本方式执行
python mod.py
2)以模块方式执行
python -m mod
四、包
一个 包 就是一个含有__init__.py文件的目录。
包与模块之间的包含关系是:一个包可以包含子包或子模块,但一个模块却不能包含子包和子模块。
1、包名
与模块名类似,包名的命名规则也与变量名相同。
此外,需要特别注意的是:如果在同一个目录下,存在两个同名的包和模块,那么导入时只会识别包,而忽略模块。(参考 specification for packages 中的 『What If I Have a Module and a Package With The Same Name?』)
例如,在目录dir下新建一个文件spam.py(即模块spam),此时import spam会导入模块spam:
$ cd dir/
$ touch spam.py
$ python
...
>>> import spam
>>> spam
如果在目录dir下再新建一个含有__init__.py文件的目录spam(即包spam),此时import spam则会导入包spam(而不再是模块spam):
$ mkdir spam && touch spam/__init__.py
$ python
...
>>> import spam
>>> spam
2、包属性
包属性与模块属性非常相似,也分为 固有属性 和 新增属性。
1)固有属性
与模块相比,包的 固有属性 仅多了一个__path__属性,其他属性完全一致(含义也类似)。
__path__属性即包的路径(列表),用于在导入该包的子包或子模块时作为搜索路径;修改一个包的__path__属性可以扩展该包所能包含的子包或子模块。(参考 Packages in Multiple Directories)
例如,在dir目录下新建一个包pkg(包含一个模块mod),显然在包pkg中只能导入一个子模块mod:
$ mkdir pkg && touch pkg/__init__.py
$ touch pkg/mod.py
$ python
...
>>> import pkg.mod # 可以导入子模块mod
>>> import pkg.mod_1 # 不能导入子模块mod_1
Traceback (most recent call last):
File "", line 1, in
ImportError: No module named mod_1
如果在dir目录下再新建一个包pkg_1(包含一个模块mod_1):
$ mkdir pkg_1 && touch pkg_1/__init__.py
$ touch pkg_1/mod_1.py
并且在pkg/__init__.py中修改包pkg的__path__属性:
print('before:', __path__)
__path__.append(__path__[0].replace('pkg', 'pkg_1')) # 将“包pkg_1所在路径”添加到包pkg的__path__属性中
print('after:', __path__)
此时,在包pkg中就可以导入子模块mod_1(仿佛子模块mod_1真的在包pkg中):
$ python
...
>>> import pkg.mod # 可以导入子模块mod
('before:', ['pkg'])
('after:', ['pkg', 'pkg_1'])
>>> import pkg.mod_1 # 也可以导入子模块mod_1
2)新增属性
包的 新增属性 包括两部分:静态的新增属性和动态的新增属性。
静态的新增属性是指:在__init__.py的顶层(top-level),由赋值语句(如import、=、def和class)创建的属性。这部分与模块的新增属性一致。
动态的新增属性是指:在执行导入语句后动态添加的新增属性。具体而言,如果有一个导入语句导入了某个包pkg中的子模块submod(或子包subpkg),那么被导入的子模块submod(或子包subpkg)将作为一个属性,被动态添加到包pkg的新增属性当中。
以包含模块mod的包pkg为例:
>>> import pkg
>>> dir(pkg)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__']
>>> import pkg.mod # 该语句导入了包pkg中的模块mod
>>> dir(pkg) # mod成为了包pkg的“动态的新增属性”
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'mod']
3、可导出的公有属性
在公有属性方面,包与模块的行为完全一致,当然别忘了包还有 动态的新增属性。
4、其他
1)导入语句
加入包的概念以后,导入语句的风格(与仅有模块时相比)不变,但是语法上有一些细微差异:用“.”来表示包与模块之间的包含关系;可操作的对象扩充为 包、模块 和 属性。
下面是涉及包时,一些典型的导入语句:
- import package
- import package.module
- import package.subpackage
- import package.subpackage.module
- from packagae import module
- from packagae import subpackagae
- from packagae import subpackagae.module或from packagae.subpackagae import module
- from packagae.module import attribute
- from packagae.subpackagae.module import attribute
2)__init__.py
关于包的__init__.py,有以下几点总结:
- 一般为空即可
- 有时也可以放置一些初始化代码,用于在包加载时执行
- 少数情况下,还可以用于定制包的一些属性(如__all__、__path__等)
五、导入原理
模块与包的导入原理几乎完全一致,因此下面以模块为主进行讨论,仅在有显著差异的地方对包作单独说明。
1、导入依赖
对于模块M而言,根据导入语句的不同(指明了模块M是否在一个包中),可能存在导入依赖的问题:
- import M
模块M不在一个包中,因此无导入依赖:直接以“M”为 完整名(fully qualified name)导入模块M
- import A.B.M或者from A.B import M
模块M在一个子包B中,而子包B又在一个包A中,因此存在导入依赖:会首先以“A”为 完整名 导入包A,接着以“A.B”为 完整名 导入子包B,最后以“A.B.M”为 完整名 导入模块M。
2、导入过程
一个模块的导入过程主要分三步:搜索、加载 和 名字绑定。(具体参考 The import statement)
1)搜索
搜索 是整个导入过程的核心,也是最为复杂的一步。对于被导入模块M,按照先后顺序,搜索的处理步骤为:
- 在缓存 sys.modules 中查找模块M,若找到则直接返回模块M
- 否则,顺序搜索 sys.meta_path,逐个借助其中的 finder 来查找模块M,若找到则加载后返回模块M
- 否则,如果模块M在一个包P中(如import P.M),则以P.__path__为搜索路径进行查找;如果模块M不在一个包中(如import M),则以 sys.path 为搜索路径进行查找
2)加载
正如 『搜索』 步骤中所述,对于找到的模块M:如果M在缓存 sys.modules 中,则直接返回;否则,会加载M。
加载 是对模块的初始化处理,包括以下步骤:
- 设置属性:包括__name__、__file__、__package__和__loader__(对于包,则还有__path__)
- 编译源码:将模块文件(对于包,则是其对应的__init__.py文件)编译为字节码(*.pyc),如果字节码文件已存在且仍然是最新的,则不会重编
- 执行字节码:执行编译生成的字节码(即模块文件或__init__.py文件中的语句)
有一点值得注意的是,加载不只是发生在导入时,还可以发生在 reload() 时。
3)名字绑定
加载完importee模块后,作为最后一步,import语句会为 导入的对象 绑定名字,并把这些名字加入到importer模块的名字空间中。其中,导入的对象 根据导入语句的不同有所差异:
- 如果导入语句为import obj,则对象obj可以是包或者模块
- 如果导入语句为from package import obj,则对象obj可以是package的子包、package的属性或者package的子模块
- 如果导入语句为from module import obj,则对象obj只能是module的属性
3、更多细节
根据 The import statement 中的描述,以下是导入原理对应的Python伪码:
import sys
import os.path
def do_import(name):
'''导入'''
parent_pkg_name = name.rpartition('.')[0]
if parent_pkg_name:
parent_pkg = do_import(parent_pkg_name)
else:
parent_pkg = None
return do_find(name, parent_pkg)
def do_find(name, parent_pkg):
'''搜索'''
if not name:
return None
# step 1
if name in sys.modules:
return sys.modules[name]
else:
# step 2
for finder in sys.meta_path:
module = do_load(finder, name, parent_pkg)
if module:
return module
# step 3
src_paths = parent_pkg.__path__ if parent_pkg else sys.path
for path in src_paths:
if path in sys.path_importer_cache:
finder = sys.path_importer_cache[path]
if finder:
module = do_load(finder, name, parent_pkg)
if module:
return module
else:
# handled by an implicit, file-based finder
else:
finder = None
for callable in sys.path_hooks:
try:
finder = callable(path)
break
except ImportError:
continue
if finder:
sys.path_importer_cache[path] = finder
elif os.path.exists(path):
sys.path_importer_cache[path] = None
else:
sys.path_importer_cache[path] = # a finder which always returns None
if finder:
module = do_load(finder, name, parent_pkg)
if module:
return module
raise ImportError
def do_load(finder, name, parent_pkg):
'''加载'''
path = parent_pkg.__path__ if parent_pkg else None
loader = finder.find_module(name, path)
if loader:
return loader.load_module(name)
else:
return None
4、sys.path
正如 『导入过程』 中所述,sys.path是 不在包中的模块(如import M)的“搜索路径”。在这种情况下,控制sys.path就能控制模块的导入过程。
sys.path 是一个路径名的列表,按照先后顺序,其中的路径主要分为以下四块:
- 程序主目录(默认定义):如果是以脚本方式启动的程序,则为 启动脚本所在目录;如果在交互式命令行中,则为 当前目录。
- PYTHONPATH目录(可选扩展):以 os.pathsep 分隔的多个目录名,即环境变量os.environ['PYTHONPATH'](类似shell环境变量PATH)
- 标准库目录(默认定义):Python标准库所在目录(与安装目录有关)
- .pth文件目录(可选扩展):以“.pth”为后缀的文件,其中列有一些目录名(每行一个目录名),用法参考 site
为了控制sys.path,可以有三种选择:
- 直接修改sys.path列表
- 使用PYTHONPATH扩展
- 使用.pth文件扩展
六、重新加载
关于导入,还有一点非常关键:加载只在第一次导入时发生。这是Python特意设计的,因为加载是个代价高昂的操作。
通常情况下,如果模块没有被修改,这正是我们想要的行为;但如果我们修改了某个模块,重复导入不会重新加载该模块,从而无法起到更新模块的作用。有时候我们希望在 运行时(即不终止程序运行的同时),达到即时更新模块的目的,内建函数 reload() 提供了这种 重新加载 机制。
关键字reload与import不同:
- import是语句,而reload是内建函数
- import使用 模块名,而reload使用 模块对象(即已被import语句成功导入的模块)
重新加载(reload(module))有以下几个特点:
- 会重新编译和执行模块文件中的顶层语句
- 会更新模块的名字空间(字典 M.__dict__):覆盖相同的名字(旧的有,新的也有),保留缺失的名字(旧的有,新的没有),添加新增的名字(旧的没有,新的有)
- 对于由import M语句导入的模块M:调用reload(M)后,M.x为 新模块 的属性x(因为更新M后,会影响M.x的求值结果)
- 对于由from M import x语句导入的属性x:调用reload(M)后,x仍然是 旧模块 的属性x(因为更新M后,不会影响x的求值结果)
- 如果在调用reload(M)后,重新执行import M(或者from M import x)语句,那么M.x(或者x)为 新模块 的属性x
七、相对导入
严格来说,模块(或包)的导入方式分为两种:绝对导入 和 相对导入。以上讨论的导入方式都称为 绝对导入,这也是Python2.7的默认导入方式。相对导入是从Python2.5开始引入的,主要用于解决“用户自定义模块可能会屏蔽标准库模块”的问题(参考 Rationale for Absolute Imports)。
相对导入 使用前导的“.”来指示importee(即被导入模块或包)与importer(当前导入模块)之间的相对位置关系。相对导入 只能使用from import风格的导入语句,import 风格的导入语句只能用于 绝对导入。(相对导入的更多细节,请参考 PEP 328)
1、导入语句
例如有一个包的布局如下:
pkg/
__init__.py
subpkg1/
__init__.py
modX.py
modY.py
subpkg2/
__init__.py
modZ.py
modA.py
假设当前在文件modX.py或subpkg1/__init__.py中(即当前包为subpkg1),那么下面的导入语句都是相对导入:
from . import modY # 从当前包(subpkg1)中导入模块modY
from .modY import y # 从当前包的模块modY中导入属性y
from ..subpkg2 import modZ # 从当前包的父包(pkg)的包subpkg2中导入模块modZ
from ..subpkg2.modZ import z # 从当前包的父包的包subpkg2的模块modZ中导入属性z
from .. import modA # 从当前包的父包中导入模块modA
from ..modA import a # 从当前包的父包的模块modA中导入属性a
2、导入原理
与绝对导入不同,相对导入的导入原理比较简单:根据 模块的__name__属性 和 由“.”指示的相对位置关系 来搜索并加载模块(参考 Relative Imports and __name__)。
3、直接执行
由于相对导入会用到模块的__name__属性,而在直接执行的主模块中,__name__值为__main__(没有包与模块的信息),所以在主模块中:尽量全部使用绝对导入。
如果非要使用相对导入,也可以在顶层包(top-level package)的外部目录下,以模块方式执行主模块:python -m pkg.mod(假设顶层包为pkg,mod为主模块,其中使用了相对导入)。(具体参考 PEP 366) |
|