Python 核心编程(第二版)——模块

1. 什么是模块?

Python 允许 "调入" 一个模块, 允许使用其他模块的属性来利用之前的工作成果, 实现代码重用.这个把其他模块中属性附加到你的模块中的操作叫做导入(import) 。那些自我包含并且有组织的代码片断就是模块( module )。

2. 模块和文件

如果说模块是按照逻辑来组织 Python 代码的方法, 那么文件便是物理层上组织模块的方法。因此, 一个文件被看作是一个独立模块, 一个模块也可以被看作是一个文件。 模块的文件名就是模块的名字加上扩展名 .py。

从基本概念来说, 一个名称空间就是一个从名称到对象的关系映射集合。模块名称是它们的属性名称中的一个重要部分。给定一个模块名之后, 只可能有一个模块被导入到 Python 解释器中, 所以在不同模块间不会出现名称交叉现象; 所以每个模块都定义了它自己的唯一的名称空间。即使属性之间有名称冲突, 但它们的完整授权名称(fully qualified name)——通过句点属性标识指定了各自的名称空间 - 防止了名称冲突的发生。

路径搜索和搜索路径是两个不同的概念, 前者是指查找某个文件的操作, 后者是去查找一组目录。

默认搜索路径是在编译或是安装时指定的。它可以在一个或两个地方修改,一个是启动 Python 的 shell 或命令行的PYTHONPATH 环境变量。该变量的内容是一组用冒号分割的目录路径。切记, 搜索路径在不同系统下一般是不同的。如果你知道你需要导入的模块是什么,而它的路径不在搜索路径里, 那么只需要调用列表的 append() 方法即可,例如:sys.path.append('/home/wesc/py/lib')。

3. 名称空间

名称空间是名称(标识符)到对象的映射。 向名称空间添加名称的操作过程涉及到绑定标识符到指定对象的操作(以及给该对象的引用计数加 1 )。《Python 语言参考》(Python Language Reference)有如下的定义: 改变一个名字的绑定叫做重新绑定, 删除一个名字叫做解除绑定。

在执行期间有两个或三个活动的名称空间。 这三个名称空间分别是局部名称空间, 全局名称空间和内建名称空间, 但局部名称空间在执行期间是不断变化的, 所以我们说"两个或三个"。 从名称空间中访问这些名字依赖于它们的加载顺序, 或是系统加载这些名称空间的顺序。

Python 解释器首先加载内建名称空间。 它由 __builtins__ 模块中的名字构成。 随后加载执行模块的全局名称空间, 它会在模块开始执行后变为活动名称空间。如果在执行期间调用了一个函数, 那么将创建出第三个名称空间, 即局部名称空间。 我们可以通过 globals() 和 locals() 内建函数判断出某一名字属于哪个名称空间。

核心笔记:__builtins__ 和 __builtin__

__builtins__ 模块和 __builtin__ 模块不能混淆。

__builtins__ 模块包含内建名称空间中内建名字的集合。 其中大多数(如果不是全部的话)来自 __builtin__ 模块, 该模块包含内建函数, 异常以及其他属性。在标准 Python 执行环境下,__builtins__ 包含 __builtin__ 的所有名字。

名称空间与变量作用域比较——名称空间是纯粹意义上的名字和对象间的映射关系, 而作用域还指出了从用户代码的哪些物理位置可以访问到这些名字。

注意每个名称空间是一个自我包含的单元。但从作用域的观点来看, 事情是不同的. 所有名称空间的名称都在局部作用范围内。局部作用范围以外的所有名称都在全局作用范围内。还要记得在程序执行过程中, 局部名称空间和作用域会随函数调用而不断变化, 而全局名称空间是不变的。

名称查找,确定作用域,覆盖——那么确定作用域的规则是如何联系到名称空间的呢? 它所要做的就是名称查询. 访问一个属性时, 解释器必须在三个名称空间中的一个找到它。 首先从局部名称空间开始, 如果没有找到, 解释器将继续查找全局名称空间. 如果这也失败了, 它将在内建名称空间里查找。如果最后的尝试也失败了, 你会得到这样的错误:

1 >>> foo
2 Traceback (innermost last): File "<stdin>", line 1, in ?
3 NameError: foo
View Code

这个错误信息体现了先查找的名称空间是如何"遮蔽"其他后搜索的名称空间的。 这体现了名称覆盖的影响。

无限制的名称空间——Python 的一个有用的特性在于你可以在任何需要放置数据的地方获得一个名称空间。

4. 导入模块

使用 import 语句导入模块, 它的语法如下所示:

import module1
import module2[
:
import moduleN


也可以在一行内导入多个模块, 像这样:
import module1[, module2[,... moduleN]]

核心风格: import 语句的模块顺序

我们推荐所有的模块在 Python 模块的开头部分导入。 而且最好按照这样的顺序:
􀁺 Python 标准库模块
􀁺 Python 第三方模块
􀁺 应用程序自定义模块

然后使用一个空行分割这三类模块的导入语句。 这将确保模块使用固定的习惯导入, 有助于减少每个模块需要的 import 语句数目。

解释器执行到这条语句, 如果在搜索路径中找到了指定的模块, 就会加载它。该过程遵循作用域原则, 如果在一个模块的顶层导入, 那么它的作用域就是全局的; 如果在函数中导入, 那么它的作用域是局部的。如果模块是被第一次导入, 它将被加载并执行。

可以在你的模块里导入指定的模块属性。 也就是把指定名称导入到当前作用域。 使用from-import 语句可以实现我们的目的, 它的语法是:

from module import name1[, name2[,... nameN]]

从一个模块导入许多属性时,import 行会越来越长, 直到自动换行, 而且需要一个 \ 。或者可以选择使用多行的 from-import 语句:

from Tkinter import Tk, Frame, Button, Entry, Canvas, Text
from Tkinter import LEFT, DISABLED, NORMAL, RIDGE, END

扩展的 import 语句(as)——有时候你导入的模块或是模块属性名称已经在你的程序中使用了, 或者你不想使用导入的名字。这已经成为 Python 程序员的一个普遍需求:使用自己想要的名字替换模块的原始名称。一个普遍的解决方案是把模块赋值给一个变量:

>>> import longmodulename
>>> short = longmodulename
>>> del longmodulename

使用扩展的 import , 你就可以在导入的同时指定局部绑定名称。例如:

1 import Tkinter
2 from cgi import FieldStorage
3 . . . 可以替换为 . . .
4 import Tkinter as tk
5 from cgi import FieldStorage as form
View Code

5. 模块导入的特性

 加载模块会导致这个模块被"执行"。 也就是被导入模块的顶层代码将直接被执行。 这通常包括设定全局变量以及类和函数的声明。 如果有检查 __name__ 的操作, 那么它也会被执行。

导入和加载——一个模块只被加载一次, 无论它被导入多少次。 这可以阻止多重导入时代码被多次执行。

导入到当前名称空间的名称——调用 from-import 可以把名字导入当前的名称空间里去, 这意味着你不需要使用属性/句点属性标识来访问模块的标识符。可以把指定模块的所有名称导入到当前名称空间里,如: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__ 不会有任何变化,所以这是被禁止的。

警告框架——应用程序(员)接口(Application programmers' interface , API),程序员应该有从Python 程序(通过调用 warnings 模块)或是 C 中(通过 PyErr_Warn() 调用)发布警告的能力。这个框架的另个部分是一些警告异常类的集合。 Warning 直接从 Exception 继承, 作为所有警告的基类: UserWarning , DeprecationWarning , SyntaxWarning , 以及 RuntimeWarning 。

另一个组件是警告过滤器, 由于过滤有多种级别和严重性, 所以警告的数量和类型应该是可控制的。 警告过滤器不仅仅收集关于警告的信息(例如行号, 警告原因等等), 而且还控制是否忽略警告, 是否显示——自定义的格式——或者转换为错误(生成一个异常)。

警告会有一个默认的输出显示到 sys.stderr , 不过有钩子可以改变这个行为, 例如,当运行会引发警告的 Python 脚本时,可以记录它的输出记录到日志文件中,而不是直接显示给终端用户。Python 还提供了一个可以操作警告过滤器的 API 。

从ZIP文件中导入模块——在 2.3 版中, Python 加入了从 ZIP 归档文件导入模块的功能。 如果你的搜索路径中存在一个包含 Python 模块(.py, .pyc, or .pyo 文件)的 .zip 文件, 导入时会把 ZIP 文件当作目录处理, 在文件中搜索模块。

如果要导入的一个 ZIP 文件只包含 .py 文件, 那么 Python 不会为其添加对应的 .pyc 文件,这意味着如果一个 ZIP 归档没有匹配的 .pyc 文件时, 导入速度会相对慢一点。

“新的”导入钩子——Python 2.3 引入的新导入钩子,从而简化了这个操作。 你只需要编写可调用的 import 类, 然后通过 sys 模块"注册"(或者叫"安装")它。你需要两个类: 一个查找器和一个载入器。 这些类的实例接受一个参数:模块或包的全名称。查找器实例负责查找你的模块, 如果它找到, 那么它将返回一个载入器对象。查找器可以接受一个路径用以查找子包(subpackages) 。载入器会把模块载入到内存。它负责完成创建一个 Python 模块所需要的一切操作, 然后返回模块。

6. 模块内建函数

__import__() 函数, 它作为实际上导入模块的函数, 这意味着 import 语句调用 __import__() 函数完成它的工作。提供这个函数是为了让有特殊需要的用户覆盖它, 实现自定义的导入算法。

__import__() 的语法是:
__import__(module_name[, globals[, locals[, fromlist]]])

module_name 变量是要导入模块的名称, globals 是包含当前全局符号表的名字的字典,locals 是包含局部符号表的名字的字典, fromlist 是一个使用 from-import 语句所导入符号的列表。globals , locals , 以及 fromlist 参数都是可选的, 默认分别为 globals() , locals() 和 [] 。

globals() 和 locals() 内建函数分别返回调用者全局和局部名称空间的字典。 在一个函数内部, 局部名称空间代表在函数执行时候定义的所有名字, locals() 函数返回的就是包含这些名字的字典。 globals() 会返回函数可访问的全局名字。在全局名称空间下, globals() 和 locals() 返回相同的字典, 因为这时的局部名称空间就是全局空间。

reload() 内建函数可以重新导入一个已经导入的模块。 它的语法如下:

reload(module)

module 是你想要重新导入的模块。使用 reload() 的时候有一些标准。 首先模块必须是全部导入(不是使用 from-import), 而且它必须被成功导入。另外 reload() 函数的参数必须是模块自身而不是包含模块名的字符串。

模块中的代码在导入时被执行, 但只执行一次. 以后执行 import 语句不会再次执行这些代码,只是绑定模块名称。 而 reload() 函数不同。

7. 包

包是一个有层次的文件目录结构, 它定义了一个由模块和子包组成的 Python 应用程序执行环境。Python 1.5 加入了包, 用来帮助解决如下问题:

􀁺 为平坦的名称空间加入有层次的组织结构
􀁺 允许程序员把有联系的模块组合到一起
􀁺 允许分发者使用目录结构而不是一大堆混乱的文件
􀁺 帮助解决有冲突的模块名称

包同样支持 from-import all 语句:

from package.module import *

然而, 这样的语句会导入哪些文件取决于操作系统的文件系统. 所以我们在__init__.py 中加入 __all__ 变量. 该变量包含执行这样的语句时应该导入的模块的名字. 它由一个模块名字符串列表组成.。

绝对导入——包的使用越来越广泛, 很多情况下导入子包会导致和真正的标准库模块发生(事实上是它们的名字)冲突。 包模块会把名字相同的标准库模块隐藏掉, 因为它首先在包内执行相对导入, 隐藏掉标准库模块。为此, 所有的导入现在都被认为是绝对的, 也就是说这些名字必须通过 Python 路径(sys.path 或是 PYTHONPATH )来访问。这个决定的基本原理是子包也可以通过 sys.path 访问。从 Python 2.7 开始, 绝对导入特性将成为默认功能。

相对导入——import 语句总是绝对导入的, 所以相对导入只应用于 from-import 语句。语法的第一部分是一个句点, 指示一个相对的导入操作。 之后的其他附加句点代表当前 from起始查找位置后的一个级别。

8. 模块的其他特性

 自动载入的模块——当 Python 解释器在标准模式下启动时, 一些模块会被解释器自动导入, 用于系统相关操作。唯一一个影响你的是 __builtin__ 模块, 它会正常地被载入, 这和 __builtins__ 模块相同。sys.modules 变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典, 模块名作为键, 它们的位置作为值。

阻止属性导入——如果你不想让某个模块属性被 "from module import *" 导入 , 那么你可以给你不想导入的属性名称加上一个下划线( _ )。 不过如果你导入了整个模块或是你显式地导入某个属性(例如 import  foo._bar ), 这个隐藏数据的方法就不起作用了。

模块执行——有很多方法可以执行一个 Python 模块: 通过命令行或 shell , execfile() , 模块导入, 解释器的 -m 选项, 等等。下边这些模块可能是你在处理 Python 模块导入时会用到的辅助模块。

􀁺 imp - 这个模块提供了一些底层的导入者功能。

􀁺 modulefinder - 该模块允许你查找 Python 脚本所使用的所有模块。你可以使用其中的ModuleFinder 类或是把它作为一个脚本执行, 提供你要分析的(另个) Python 模块的文件名。
􀁺 pkgutil - 该模块提供了多种把 Python 包打包为一个"包"文件分发的方法。 类似 site模块, 它使用 *.pkg 文件帮助定义包的路径, 类似 site 模块使用的 *.pth 文件。
􀁺 site - 和 *.pth 文件配合使用, 指定包加入 Python 路径的顺序, 例如 sys.path ,PYTHONPATH 。你不需要显式地导入它, 因为 Python 导入时默认已经使用该模块。你可能需要使用 -S 开关在 Python 启动时关闭它。你也可以完成一些 site 相关的自定义操作,例如在路径导入完成后在另个地方尝试。
􀁺 zipimport - 你可以使用该模块导入 ZIP 归档文件中的模块。 需要注意的是该功能已经"自动"开启, 所以你不需要在任何应用中使用它。在这里我们提出它只是作为参考。
􀁺 distutils - 该模块提供了对建立、 安装、分发 Python 模块和包的支持。

 

posted @ 2017-10-19 17:33  Christal_11  阅读(203)  评论(0编辑  收藏  举报