今天继续分享第十二章内容:模块,可以关注我的微信公众号【Python Dao】,也可以扫描下方二维码关注我,我们一起学习交流。
12.1 什么是模块
模块支持从逻辑上组织 Python 代码。 当代码量变得庞大时,应该把代码分成一些有组织的代码段,前提是保证它们的彼此交互。 这些代码片段相互间有一定的联系, 可能是一个包含数据成员和方法的类,也可能是一组相关但彼此独立的操作函数。 这些代码段是共享的,所以Python 允许 "调入" 一个模块,允许使用其他模块的属性来利用之前的工作成果,实现代码重用。
这个把其他模块中属性附加到你的模块中的操作叫做导入(import) 。
那些自我包含并且有组织的代码片断就是模块( module )。
12.2 模块和文件
如果说模块是按照逻辑来组织 Python 代码的方法, 那么文件便是物理层上组织模块的方法。因此, 一个文件被看作是一个独立模块, 一个模块也可以被看作是一个文件。 模块的文件名就是模块的名字加上扩展名 .py 。
在 Python 中导入的是模块或模块属性。
名称空间是一个从名称到对象的关系映射集合。
路径搜索:指查找某个文件的操作
搜索路径:查找一组目录
设置Python默认搜索路径:
- 启动 Python 的 shell 或命令行的 PYTHONPATH 环境变量
- 解释器启动之后,通过sys 模块的 sys.path 变量来进行修改
如果有多个相同模块,解释器会使用沿搜索路径顺序找到的第一个模块。
使用 sys.modules 可以找到当前导入了哪些模块和它们来自什么地方。 sys.modules 是一个字典, 使用模块名作为键( key) , 对应物理地址作为值( value )。
12.3 名称空间
名称空间是名称(标识符)到对象的映射。
《Python 语言参考》(Python Language Reference)有如下的定义: 改变一个名字的绑定叫做重新绑定, 删除一个名字叫做解除绑定。
在之前学过在执行期间有两个或三个活动的名称空间。 分别是局部名称空间、 全局名称空间和内建名称空间,但局部名称空间在执行期间是不断变化的, 所以我们说"两个或三个"。 从名称空间中访问这些名字依赖于它们的加载顺序, 或是系统加载这些名称空间的顺序。
Python 解释器首先加载内建名称空间。 它由 __builtins__ 模块中的名字构成。 随后加载执行模块的全局名称空间, 它会在模块开始执行后变为活动名称空间。 于是就有了两个活动的名称空间。
在执行期间调用了一个函数, 那么将创建出第三个名称空间, 即局部名称空间。
可通过 globals() 和 locals() 内建函数判断出某一名字属于哪个名称空间。
核心笔记: __builtins__ 和 __builtin__
__builtins__ 模块和 __builtin__ 模块不能混淆。 __builtins__ 模块包含内建名称空间中内建名字的集合。 其中大多数(如果不是全部的话)来自 __builtin__ 模块, 该模块包含内建函数, 异常以及其他属性。 在标准 Python 执行环境下,__builtins__ 包含 __builtin__ 的所有名字。
名称空间和变量作用域的关系:名称空间是纯粹意义上的名字和对象间的映射关系, 作用域除了映射关系,还指出了从用户代码的哪些物理位置可以访问到这些名字。
解释器访问一个属性时的查询步骤:首先从局部名称空间开始, 如果没有找到,继续查找全局名称空间,还是没找到,继续查找内建名称空间,没找到,抛出 NameError
错误。
Python 的一个有用的特性在于你可以在任何需要放置数据的地方获得一个名称空间。可以在任何时候给函数添加属性(使用熟悉的句点属性标识)。你可以把任何想要的东西放入一个名称空间里。
def foo():
pass
# 添加文档
foo.__doc__ = 'Oops, forgot to add doc str above!'
# 添加版本号
foo.version = 0.2
12.4 导入模块
使用 import 语句导入模块, 语法如下:
import module1
import module2[
:
import moduleN
可以在一行内导入多个模块,可读性较差,推荐使用上一种:
import module1[, module2[,... moduleN]]
核心风格: import 语句的模块顺序
推荐所有的模块在 Python 模块的开头部分导入。 而且最好按照这样的顺序:
- Python 标准库模块
- Python 第三方模块
- 应用程序自定义模块
然后使用一个空行分割这三类模块的导入语句。 这将确保模块使用固定的习惯导入, 有助于减少每个模块需要的 import 语句数目。 其他的提示请参考《 Python 风格指南》(Python’s Style Guide), PEP8 。
解释器执行导入语句遵循作用域原则:如果在一个模块的顶层导入, 其作用域为全局作用域; 在函数中导入,作用域为局部作用域。
如果模块是被第一次导入, 它将被加载并执行。
可以只导入指定的模块属性,语法如下:
from module import name1[, name2[,... nameN]]
# 如果导入内容太多,则可以多行导入,使用反斜线换行
from Tkinter import Tk, Frame, Button, Entry, Canvas, \
Text, LEFT, DISABLED, NORMAL, RIDGE, END
# 也可以使用多行的from-import语句
from Tkinter import Tk, Frame, Button, Entry, Canvas, Text
from Tkinter import LEFT, DISABLED, NORMAL, RIDGE, END
# 不提倡使用
from Tkinter import *
扩展的 import 语句(as)
import Tkinter
from cgi import FieldStorage
# 可以替换为
import Tkinter as tk
from cgi import FieldStorage as form
12.5 模块导入的特性
加载模块会导致这个模块被"执行"。即是被导入模块的顶层代码将直接被执行。 这通常包括设定全局变量以及类和函数的声明。 如果有检查 __name__ 的操作, 那么它也会被执行。当然, 这样的执行可能不是我们想要的结果。 应该把尽可能多的代码封装到类或者函数中。明确地说,只把函数和模块定义或者类定义放入模块的顶层是良好的模块编程习惯。
一个模块只被加载一次, 无论它被导入多少次。加载只在第一次导入时发生。
调用 from-import 可以把名字导入当前的名称空间里去, 这意味着你不需要使用属性/句点属性标识来访问模块的标识符。例如:
from module import var
在你的模块中就可以直接使用 var
了。
也可以把指定模块的所有名称导入到当前名称空间里:
from module import *
但是这是不推荐的。
核心风格:限制使用 "from module import "
在实践中, 我们认为 "from module import " 不是良好的编程风格, 因为它"污染"当前名称空间, 而且很可能覆盖当前名称空间中现有的名字; 但如果某个模块有很多要经常访问的变量或者模块的名字很长, 这也不失为一个方便的好办法。
我们只在两种场合下建议使用这样的方法, 一个场合是:目标模块中的属性非常多, 反复键入模块名很不方便** , 例如 Tkinter (Python/Tk) 和 NumPy (Numeric Python) 模块 , 可能还有socket 模块。另一个场合是在交互解释器下, 因为这样可以减少输入次数。
只从模块导入名字的另一个副作用是那些名字会成为局部名称空间的一部分。 这可能导致覆盖一个已经存在的具有相同名字的对象。 而且对这些变量的改变只影响它的局部拷贝而不是所导入模块的原始名称空间。 也就是说, 绑定只是局部的而不是整个名称空间。
__future__ 指令
由于改进, 新特性, 以及当前特性增强, 某些变化会影响到当前功能。 所以为了让 Python 程序员为新事物做好准备, Python 实现了 __future__ 指令。
使用 from-import 语句"导入"新特性, 用户可以尝试一下新特性或特性变化, 以便在特性固定下来的时候修改程序。 它的语法是:
from __future__ import new_feature
**只使用 import __future__
不会导入任何新特性,必须显示地导入指定特性。
警告框架
和 __future__ 指令类似, 有必要去警告用户不要使用一个即将改变或不支持的操作, 这样他们会在新功能正式发布前采取必要措施。
首先是应用程序(员)接口(Application programmers' interface , API)。 程序员应该有从Python 程序(通过调用 warnings 模块)或是 C 中(通过 PyErr_Warn() 调用)发布警告的能力。
此框架的另个部分是一些警告异常类的集合。 Warning 直接从 Exception 继承, 作为所有警告的基类: UserWarning , DeprecationWarning , SyntaxWarning , 以及 RuntimeWarning 。
另一个组件是警告过滤器, 由于过滤有多种级别和严重性, 所以警告的数量和类型应该是可控制的。警告过滤器收集关于警告的信息(例如行号, 警告原因等等), 控制是否忽略警告, 是否显示自定义的格式或者转换为错误(生成一个异常)。
警告有一个默认的输出显示到 sys.stderr , 有钩子可以改变这个行为,Python 提供了一个可以操作警告过滤器的 API 。
命令行也可以控制警告过滤器。 在启动 Python 解释器的时候使用 -W
选项。
从 ZIP 文件中导入模块
在 2.3 版中, Python 加入了从 ZIP 归档文件导入模块的功能。 如果你的搜索路径中存在一个包含 Python 模块(.py, .pyc, or .pyo 文件)的 .zip 文件, 导入时会把 ZIP 文件当作目录处理, 在文件中搜索模块。
可以使用新方式导入,你需要两个类: 一个查找器和一个载入器。 这些类实例接受一个参数:模块或包的全名称。查找器实例负责查找你的模块, 如果它找到,返回一个载入器对象。查找器可以接受一个路径用以查找子包(subpackages) 。载入器会把模块载入到内存。它负责完成创建一个 Python 模块所需要的一切操作, 然后返回模块。
这些实例被加入到 sys.path_hooks
。 sys.path_importer_cache
只是用来保存这些实例, 这样就只需要访问 path_hooks
一次。 最后, sys.meta_path
用来保存一列需要在查询 sys.path
之前访问的实例, 这些是为那些已经知道位置而不需要查找的模块准备的。 meta_path 是已经有了指定模块或包的载入器对象的读取器。
12.6 模块内建函数
__import__()
它作为实际上导入模块的函数, 执行 import 语句实际上是调用 __import__() 函数完成的。提供这个函数是为有特殊需要的用户覆盖它, 实现自定义的导入算法。
语法如下:
# module_name 变量是要导入模块的名称
# globals 是包含当前全局变量名的字典
# locals 是包含局部变量名的字典
# fromlist 是一个使用 from-import 语句所导入模块成员名的列表。
__import__(module_name[, globals[, locals[, fromlist]]])
# globals, locals, 以及 fromlist 参数都是可选的, 默认分别为 globals(), locals() 和 []
# 调用 import sys 语句可以使用:
sys = __import__('sys')
globals()
返回当前命名空间中的所有可访问的全局变量名的字典。
locals()
返回当前命名空间中的所有局部变量名的字典。
在全局命名空间下, globals() 和 locals() 返回相同的字典, 因为这时的局部命名空间就是全局空间。
reload()
重新导入一个已经导入的模块。 语法如下:
reload(module) # module 是你想要重新导入的模块。
使用 reload() 时有一些标准:
首先 模块必须是全部导入(不是使用 from-import), 而且它必须被成功导入。
此外 其参数必须是模块自身而不是包含模块名的字符串。 类似 reload(sys) 而非 reload('sys')。
模块中的代码在导入时被执行, 但只执行一次. 以后执行 import 语句不会再次执行这些代码,只是绑定模块名称。 而 reload() 函数不同。
12.7 包
包是一个有层次的文件目录结构, 它定义了一个由模块和子包组成的 Python 应用程序执行环境。Python 1.5 加入了包, 用来帮助解决如下问题:
- 为平坦的名称空间加入有层次的组织结构
- 允许程序员把有联系的模块组合到一起
- 允许分发者使用目录结构而不是一大堆混乱的文件
- 帮助解决有冲突的模块名称
与 类 和 模 块 相 同 , 包 也 使 用 句 点 属 性 标 识 来 访 问 他 们 的 元 素 。 使 用 标 准 的 import 和 from-import 语句导入包中的模块。
每个包 目 录 会有一个__init__.py 文 件,它是初始化模块用的,from-import 语句导入子包时也需要用到它。当使用 from package.module import *
导入包时,可以在其中加入__all__ 变量用以控制应该导入哪些内容,__all__ 变量是由一个模块名字符串组成的列表。
绝对导入:
import Analog
from Analog import dial
相对导入:
from Phone.Mobile.Analog import dial
from .Analog import dial
from ..common_util import setup
12.8 模块的其他特性
当 Python 解释器在标准模式下启动时, 一些模块会被解释器自动导入, 用于系统相关操作。唯一一个影响你的是 __builtin__ 模块, 它会正常地被载入, 这和 __builtins__ 模块相同。
sys.modules 变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典, 模块名作为键, 它们的位置作为值。
如果你不想让某个模块属性被 "from module import *
" 导入 , 那么你可以给你不想导入的属性名称加上一个下划线( _ )。但是当显示导入时,此方法将失效。
指示源代码的编码:
一个 UTF-8 编码的文件可以这样指示:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
在Python 3 中,Python源码文件默认编码为UTF-8,所以无需显示指定,如果需要其它编码,则需要指定。
避免循环导入需要指定正确的导入方式,有些导入语句需要放到局部去导入,才能成功。
执行一个 Python 模块的方法:
- 通过命令行或 shell
- execfile() ,
- 模块导入
- 解释器的 -m 选项
12.9 相关模块
在处理 Python 模块导入时可能会用到的辅助模块:
- imp - 这个模块提供了一些底层的导入者功能。
- modulefinder - 该模块允许你查找 Python 脚本所使用的所有模块。
- pkgutil - 该模块提供了多种把 Python 包打包为一个"包"文件分发的方法。
- site - 和 *.pth 文件配合使用, 指定包加入 Python 路径的顺序, 例如 sys.path , PYTHONPATH 。不需要显式导入, Python 导入时默认已经使用该模块。可使用
-S
开关在 Python 启动时关闭它。 - zipimport - 你可以使用该模块导入 ZIP 归档文件中的模块。 该功能已经"自动"开启, 你无需在任何应用中使用它。
- distutils - 该模块提供了对建立、 安装、分发 Python 模块和包的支持。 还可以帮助建立使用 C/C++ 完成的 Python 扩展。