Python基础:模块
一、概述
模块(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)
[GCC 4.6.3]'
>>> import sys as s
>>> s.version
'2.7.3 (default, Apr 10 2013, 05:46:21)
[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后缀”后的文件名,即__file__.rpartition('/')[2].split('.')[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.attributefrom 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 <arguments>
2)以模块方式执行
python -m mod <arguments>
四、包
一个 包 就是一个含有__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
<module 'spam' from 'spam.py'>
如果在目录dir下再新建一个含有__init__.py文件的目录spam(即包spam),此时import spam
则会导入包spam(而不再是模块spam):
$ mkdir spam && touch spam/__init__.py
$ python
...
>>> import spam
>>> spam
<module 'spam' from 'spam/__init__.py'>
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 "<stdin>", line 1, in <module>
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)