第12章 模块

本章主题

♦ 什么是模块

♦ 模块和文件

♦ 命名空间

♦ 导入模块

♦ 导入模块属性

♦ 模块内建函数包模块的其他特性

本章将集中介绍Python模块和如何把数据从模块中导入到编程环境中。同时也会涉及包的相关概念。模块是用来组织Python代码的方法,而包则是用来组织模块的。本章最后还会讨论一些与模块有关的其他方面的问题。

12.1 什么是模块

模块支持从逻辑上组织Python代码。当代码量变得相当大的时候,我们最好把代码分成一些有组织的代码段,前提是保证它们的彼此交互。这些代码片段相互间有一定的联系,可能是一个包含数据成员和方法的类,也可能是一组相关但彼此独立的操作函数。这些代码段是共享的,所以Python允许“调入”一个模块,允许使用其他模块的属性来利用之前的工作成果,实现代码重用。这个把其他模块中属性附加到你的模块中的操作叫做导入(import)。那些自我包含并且有组织的代码片段就是模块(module)。

12.2 模块和文件

如果说模块是按照逻辑来组织Python代码的方法,那么文件便是物理层上组织模块的方法。因此,一个文件被看作是一个独立模块,一个模块也可以被看作是一个文件。模块的文件名就是模块的名字加上扩展名。py。这里我们需要讨论一些关于模块文件结构的问题。与其他可以导入类(class)的语言不同,在Python中你导入的是模块或模块属性。

12.2.1 模块名称空间

本章的后面会详细的讨论名称空间,但从基本概念来说,一个名称空间就是一个从名称到对象的关系映射集合。我们已经明确地知道,模块名称是它们的属性名称中的一个重要部分。例如string模块中的atoi()函数就是string. atoi() 。给定一个模块名之后,只可能有一个模块被导入到Python解释器中,所以在不同模块间不会出现名称交叉现象,所以每个模块都定义了它自己的唯一的名称空间。如果我在我自己的模块mymodule里创建了一个atoi()函数,那么它的名字应该是mymodule. atoi()。所以即使属性之间有名称冲突,但它们的完整授权名称(fully qualified name) ——通过句点属性标识指定了各自的名称空间——防止了名称冲突的发生。

12.2.2 搜索路径和路径搜索

模块的导入需要一个叫做“路径搜索”的过程。即在文件系统“预定义区域”中查找mymodule.py文件(如果你导入mymodule的话)。这些预定义区域只不过是你的Python搜索路径的集合。路径搜索和搜索路径是两个不同的概念,前者是指查找某个文件的操作,后者是去查找一组目录。有时候导入模块操作会失败:

发生这样的错误时,解释器会告诉你它无法访问请求的模块,可能的原因是模块不在搜索路径里,从而导致了路径搜索的失败。

默认搜索路径是在编译或是安装时指定的。它可以在一个或两个地方修改。

一个是启动Python的shell或命令行的PYTHONPATH环境变量。该变量的内容是一组用冒号分割的目录路径。如果你想让解释器使用这个变量,那么请确保在启动解释器或执行Python脚本前设置或修改了该变量。

解释器启动之后,也可以访问这个搜索路径,它会被保存在sys模块的sys. path变量里。不过它已经不是冒号分割的字符串,而是包含每个独立路径的列表。下面是一个Unix机器搜索路径的样例。切记,搜索路径在不同系统下一般是不同的。

这只是个列表,所以我们可以随时随地对它进行修改。如果你知道你需要导入的模块是什么,而它的路径不在搜索路径里,那么只需要调用列表的append()方法即可,就像这样:

修改完成后,你就可以加载自己的模块了。只要这个列表中的某个目录包含这个文件,它就会被正确导入。当然,这个方法是把目录追加在搜索路径的尾部。如果你有特殊需要,那么应该使用列表的insert()方法操作。上面的例子里,我们是在交互模式下修改sys. path的,在脚本程序中也完全可以达到同样的目的。这里是使用交互模式执行时遇到的错误:

从另一方面看,你可能有一个模块的很多拷贝。这时,解释器会使用沿搜索路径顺序找到的第一个模块。

使用sys.modules可以找到当前导入了哪些模块和它们来自什么地方。和sys.path不同,sys.modules是一个字典,使用模块名作为键(key),对应物理地址作为值(value)。

12.3 名称空间

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

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

Python解释器首先加载内建名称空间。它由builtins模块中的名字构成。随后加载执行模块的全局名称空间,它会在模块开始执行后变为活动名称空间。这样我们就有了两个活动的名称空间。

核心笔记:__builtins__和__builtin__

__builtins__模块和__builtin__模块不能混淆。虽然它们的名字相似——尤其对于新手来说。__builtins__模块包含内建名称空间中内建名字的集合。其中大多数(如果不是全部的话)来自__builtin__模块,该模块包含内建函数,异常以及其他属性。在标准Python执行环境下,__builtins__包含__builtin__的所有名字。Python曾经有一个限制执行模式,允许你修改__builtins__,只保留来自__builtin__的一部分,创建一个沙盒(sandbox)环境。但是,因为它有一定的安全缺陷,而且修复它很困难,Python已经不再支持限制执行模式。(如版本2.3)

如果在执行期间调用了一个函数,那么将创建出第三个名称空间,即局部名称空间。我们可以通过globals()和locals()内建函数判断出某一名字属于哪个名称空间。我们将在本章后面详细介绍这两个函数。

12.3.1 名称空间与变量作用域比较

好了,我们已经知道了什么是名称空间,那么它与变量作用域有什么关系呢?它们看起来极其相似,事实上也确实如此。

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

图 12-1 名称空间和变量作用域

注意每个名称空间是一个自我包含的单元。但从作用域的观点来看,事情是不同的。所有局部名称空间的名称都在局部作用范围内。局部作用范围以外的所有名称都在全局作用范围内。

还要记得在程序执行过程中,局部名称空间和作用域会随函数调用而不断变化,而全局名称空间是不变的。

学完这一节后,我们建议读者在遇到名称空间的时候想想“它存在吗?”,遇到变量作用域的时候想想“我能看见它吗?”

12.3.2 名称查找、确定作用域、覆盖

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

这个错误信息体现了先查找的名称空间是如何“遮蔽”其他后搜索的名称空间的。这体现了名称覆盖的影响。图12-1的灰盒子展示了遮蔽效应。例如,局部名称空间中找到的名字会隐藏全局或内建名称空间的对应对象。这就相当于“覆盖”了那个全局变量。请参阅前面章节引入的这几行代码:

执行代码,我们将得到这样的输出:

foo()函数局部名称空间里的bar变量覆盖了全局的bar变量。虽然bar存在于全局名称空间里,但程序首先找到的是局部名称空间里的那个,所以“覆盖”了全局的那个。关于作用域的更多内容请参阅第11.8节。

12.3.3 无限制的名称空间

Python的一个有用的特性在于你可以在任何需要放置数据的地方获得一个名称空间。我们已经在前一章见到了这一特性,你可以在任何时候给函数添加属性(使用熟悉的句点属性标识)。

在本章,我们展示了模块是如何创建名称空间的,你也可以使用相同的方法访问它们:

虽然我们还没介绍面向对象编程(OOP,将在第13章介绍),但我们可以看看一个简单的“Hello World!”例子:

你可以把任何想要的东西放入一个名称空间里。像这样使用一个类(实例)是很好的,你甚至不需要知道一些关于OOP的知识(注:类似这样的变量叫做实例属性)。不管名字如何,这个实例只是被用做一个名称空间。

随着学习的深入,你会发现OOP是多么地有用,比如在运行时临时(而且重要)变量的时候!正如在《Zen of Python》中陈述的最后一条,“名称空间是一个响亮的杰出创意——那就让我们多用用它们吧!”(在交互模式解释器下导入this模块就可以看到完整的《Zen》)。

12.4 导入模块

12.4.1 import语句

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

也可以在一行内导入多个模块,像这样…

但是这样的代码可读性不如多行的导入语句。而且在性能上和生成Python字节代码时这两种做法没有什么不同。所以一般情况下,我们使用第一种格式。

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

我们推荐所有的模块在Python模块的开头部分导入。而且最好按照这样的顺序:

  • Python标准库模块

  • Python第三方模块

  • 应用程序自定义模块

然后使用一个空行分割这三类模块的导入语句。这将确保模块使用固定的习惯导入,有助于减少每个模块需要的import语句数目。其他的提示请参考“Python’s Style Guide”, PEP8。

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

如果模块是被第一次导入,它将被加载并执行。

12.4.2 from-import语句

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

12.4.3 多行导入

多行导入特性是Python 2.4为较长的from-import提供的。从一个模块导入许多属性时,import行会越来越长,直到自动换行,而且需要一个\。下面是PEP 328提供的样例代码:

你可以选择使用多行的from-import语句:

我们不提倡使用不再流行的from Tkinter import*语句(参考12. 5. 3小节的“核心风格”) 。真正的Python程序员应该使用Python的标准分组机制(圆括号)来创建更合理的多行导入语句:

你可以在PEP 328找到更多关于多行导入的内容。

12.4.4 扩展的import语句(as)

有时候你导入的模块或是模块属性名称已经在你的程序中使用了,或者你不想使用导入的名字。可能是它太长不便输入什么的,总之你不喜欢它。这已经成为Python程序员的一个普遍需求:使用自己想要的名字替换模块的原始名称。一个普遍的解决方案是把模块赋值给一个变量:

上边的例子中,我们没有使用longmodulename. attribute,而是使用short. attribute来访问相同的对象(from-imoort语句也可以解决类似的问题,参见下面的例子)。不过在程序里一遍又一遍做这样的操作是很无聊的。使用扩展的import,你就可以在导入的同时指定局部绑定名称。如下所示。

Python 2. 0加入了这个特性。不过那时“as”还不是一个关键字;Python 2. 6正式把它列为一个关键字。更多关于扩展导入语句的内容请参阅《Python语言参考手册》和PEP 221。

12.5 模块导入的特性

12.5.1 载入时执行模块

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

当然,这样的执行可能不是我们想要的结果。你应该把尽可能多的代码封装到函数。明确地说,只把函数和模块定义放入模块的顶层是良好的模块编程习惯。

更多信息请参阅第14.1.1节以及相应的“核心笔记”。

Python加入的一个新特性允许你把一个已经安装的模块作为脚本执行。(当然,执行你自己的脚本很简单[$ foo.py],但执行一个标准库或是第三方包中的模块需要一定的技巧。)你可以在第14. 4. 3一节中了解更多。

12.5.2 导入(import)和加载(load)

一个模块只被加载一次,无论它被导入多少次。这可以阻止多重导入时代码被多次执行。例如你的模块导入了sys模块,而你要导入的其他5个模块也导入了它,那么每次都加载sys(或是其他模块)不是明智之举!所以,加载只在第一次导入时发生。

12.5.3 导入到当前名称空间的名称

调用from-import可以把名字导入当前的名称空间里去,这意味着你不需要使用属性/句点属性标识来访问模块的标识符。例如,你需要访问模块module中的var名字是这样被导入的:

我们使用单个的var就可以访问它自身。把var导入到名称空间后就再没必要引用模块了。当然,你也可以把指定模块的所有名称导入到当前名称空间里:

核心风格:限制使用“from module import*”

在实践中,我们认为“from module import*”不是良好的编程风格,因为它“污染”当前名称空间,而且很可能覆盖当前名称空间中现有的名字;但如果某个模块有很多要经常访问的变量或者模块的名字很长,这也不失为一个方便的好办法。

我们只在两种场合下建议使用这样的方法,一个场合是:目标模块中的属性非常多,反复键入模块名很不方便,例如Tkinter (Python/Tk)和NumPy (Numeric Python)模块,可能还有socket模块。另一个场合是在交互解释器下,因为这样可以减少输入次数。

12.5.4 被导入到导入者作用域的名字

只从模块导入名字的另一个副作用是那些名字会成为局部名称空间的一部分。这可能导致覆盖一个已经存在的具有相同名字的对象。而且对这些变量的改变只影响它的局部拷贝而不是所导入模块的原始名称空间。也就是说,绑定只是局部的而不是整个名称空间。

这里我们提供了两个模块的代码:一个导入者,impter.py和一个被导入者,imptee. py. impter. py使用from-import语句只创建了局部绑定。

运行这个导入者程序,我们发现从被导入者的观点看,它的foo变量没有改变,即使我们在importer.py里修改了它。

唯一的解决办法是使用import和完整的标识符名称(句点属性标识)。

完成相应修改后,结果如我们所料:

12.5.5 关于__future__

回首Python 2. 0,我们认识到了由于改进、新特性和当前特性增强,某些变化会影响到当前功能。所以为了让Python程序员为新事物做好准备,Python实现了__future__指令。

使用from-import语句“导入”新特性,用户可以尝试一下新特性或特性变化,以便在特性固定下来的时候修改程序。它的语法是:

只import__future__不会有任何变化,所以这是被禁止的(事实上这是允许的,但它不会如你所想的那样启用所有特性)。你必须显示地导入指定特性。你可以在PEP 236找到更多关于__future__的资料。

12.5.6 警告框架

和__future__指令类似,有必要去警告用户不要使用一个即将改变或不支持的操作,这样他们会在新功能正式发布前采取必要措施。这个特性是很值得讨论的,我们这里分步讲解一下。

首先是应用程序(员)接口(Application programmers’ interface, API) 。程序员应该有从Python程序(通过调用warnings模块)或是C中(通过PyErr_Warn()调用)发布警告的能力。

这个框架的另个部分是一些警告异常类的集合。Warning直接从Exception继承,作为所有警告的基类,这些警告包括UserWarning, DeprecationWarning, SyntaxWarning和RuntimeWarning,都在第10章中有详细介绍。

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

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

最后,命令行也可以控制警告过滤器。你可以在启动Python解释器的时候使用-W选项。请参阅PEP 230的文档获得你的Python版本的对应开关选项。Python 2.1第一次引入警告框架。

12.5.7 从ZIP文件中导入模块

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

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

同时你也可以为。zip文件加入特定的(子)目录,例如/tmp/yolk. zip/lib只会从yolk归档的lib/子目录下导入。虽然PEP 273指定了这个特性,但事实上使用了PEP 302提供的导入钩子来实现它。

12.5.8 “新的”导入钩子

导入ZIP归档文件这一特性其实新导入钩子(import hook, PEP 302)的“第一个顾客”。我们使用了“新”这个字,因为在这之前实现自定义导入器只能是使用一些很古老的模块,它们并不会简化创建导入器。另一个解决方法是覆盖__import__(),但这并不简单,你需要(重新)实现整个导入机制。

Python 2. 3引入的新导入钩子,从而简化了这个操作。你只需要编写可调用的import类,然后通过sys模块“注册”(或者叫“安装”)它。

你需要两个类:一个查找器和一个载入器。这些类的实例接受一个参数:模块或包的全名称。查找器实例负责查找你的模块,如果它找到,那么它将返回一个载入器对象。查找器可以接受一个路径用以查找子包(subpackages) 。载入器会把模块载入到内存。它负责完成创建一个Python模块所需要的一切操作,然后返回模块。

这些实例被加入到sys. path_hookso.sys.path_importer_cache只是用来保存这些实例,这样就只需要访问path_hooks—次。最后,sys. meta_path用来保存一列需要在查询sys. path之前访问的实例,这些是为那些已经知道位置而不需要查找的模块准备的。meta-path已经有了指定模块或包的载入器对象的读取器。

12.6 模块内建函数

系统还为模块提供了一些功能上的支持,现在我们将详细讨论他们。

12.6.1 __import__()

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

__import__()的语法是:

module_name变量是要导入模块的名称,globals是包含当前全局符号表的名字的字典,locals是包含局部符号表的名字的字典,fromlist是一个使用from-import语句所导入符号的列表。

globals、locals和fromlist参数都是可选的,默认分别为globals() 、locals()和[]。

调用import sys语句可以使用下边的语句完成:

12.6.2 globals()和locals()

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

在全局名称空间下,globals()和locals()返回相同的字典,因为这时的局部名称空间就是全局空间。下边这段代码演示这两个函数的了使用:

我们只在这里访问了字典的键,因为它的值在这里没有影响(而且他们会让行变得更长更难懂)。执行这个脚本,我们得到如下的输出:

12.6.3 reload()

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

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

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

12.7 包

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

  • 为平坦的名称空间加入有层次的组织结构;

  • 允许程序员把有联系的模块组合到一起;

  • 允许分发者使用目录结构而不是一大堆混乱的文件;

  • 帮助解决有冲突的模块名称。

与类和模块相同,包也使用句点属性标识来访问他们的元素。使用标准的import和from-import语句导入包中的模块。

12.7.1 目录结构

假定我们的包的例子有如下的目录结构:

Phone是最顶层的包,Voicedta等是它的子包。我们可以这样导入子包:

你也可使用from-import实现不同需求的导入。

第一种方法是只导入顶层的子包,然后使用属性/点操作符向下引用子包树:

此外,我们可以还引用更多的子包:

事实上,你可以一直沿子包的树状结构导入:

在我们上边的目录结构中,我们可以发现很多的__init__.py文件。这些是初始化模块,from-import语句导入子包时需要用到它。如果没有用到,他们可以是空文件。程序员经常忘记为它们的包目录加入__init__. py文件,所以从Python 2. 5开始,这将会导致一个ImportWarning信息。

不过,除非给解释器传递了-Wd选项,否则它会被简单地忽略。

12.7.2 使用from-import导入包

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

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

12.7.2.1 绝对导入

包的使用越来越广泛,很多情况下导入子包会导致和真正的标准库模块发生(事实上是它们的名字)冲突。包模块会把名字相同的标准库模块隐藏掉,因为它首先在包内执行相对导入,隐藏掉标准库模块。

为此,所有的导入现在都被认为是绝对的,也就是说这些名字必须通过Python路径(sys. path或是PYTHONPATH)来访问。

这个决定的基本原理是子包也可以通过sys. path访问,例如import Phone. Mobile. Analog。在这个变化之前,从Mobile子包内模块中导入Analog是合理的。作为一个折中方案,Python允许通过在模块或包名称前置句点实现相对导入。更多信息请参阅第12.7.4节。

从Python 2. 7开始,绝对导入特性将成为默认功能(从Python 2. 5开始,你可以从__future__导入absolute_import,体验这个功能)。你可以参阅PEP 328了解更多相关内容。

12.7.2.2 相对导入

如前所述,绝对导入特性限制了模块作者的一些特权。失去了import语句的自由,必须有新的特性来满足程序员的需求。这时候,我们有了相对导入。相对导入特性稍微地改变了import语法,让程序员告诉导入者在子包的哪里查找某个模块。因为import语句总是绝对导入的,所以相对导入只应用于from-import语句。

语法的第一部分是一个句点,指示一个相对的导入操作。之后的其他附加句点代表当前from起始查找位置后的一个级别。

我们再来看看上边的例子。在Analog. Mobile. Digital,也就是Digital.py模块中,我们不能简单地使用这样的语法。下边的代码只能工作在旧版本的Python下,在新的版本中它会导致一个警告,或者干脆不能工作:

这是绝对导入的限制造成的。你需要在使用绝对导入或是相对导入中做出选择。下边是一些可行的导入方法:

从2.5版开始,相对导入被加入到了Python中。在Python 2.6中,在模块内部的导入如果没有使用相对导入,那么会显示一个警告信息。你可以在PEP 328的文档中获得更多相关信息。

12.8 模块的其他特性

12.8.1 自动载入的模块

当Python解释器在标准模式下启动时,一些模块会被解释器自动导入,用于系统相关操作。唯一一个影响你的是__builtin__模块,它会正常地被载入,这和__builtins__模块相同。

sys.modules变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典,模块名作为键,它们的位置作为值。

例如在Windows下,sys.modules变量包含大量载入的模块,我们这里截短它,只提供他们的模块名,通过调用字典的keys()方法:

Unix下载入的模块很类似:

12.8.2 阻止属性导入

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

12.8.3 不区分大小的导入

有一些操作系统的文件系统是不区分大小写的。Python 2. 1前,Python尝试在不同平台下导入模块时候“做正确的事情”,但随着MacOSX和Cygwin平台的流行,这样的不足已经不能再被忽视,而需要被清除。

在Unix(区分大小写)和Win32(不区分大小写)下,一切都很明了,但那些新的不区分大小写的系统不会被加入区分大小写的特性。PEP 235指定了这个特性,尝试解决这个问题,并避免那些其他系统上“hack”式的解决方法。底线就是为了让不区分大小写的导入正常工作,必须指定一个叫做PYTHONCASEOK的环境变量。Python会导入第一个匹配模块名(使用不区分大小写的习惯)。否则Python会执行它的原生区分大小写的模块名称匹配,导入第一个匹配的模块。

12.8.4 源代码编码

从Python 2.3开始,Python的模块文件开始支持除7位ASCII之外的其他编码。当然ASCII是默认的,你只要在你的Python模块头部加入一个额外的编码指示说明就可以让导入者使用指定的编码解析你的模块,编码对应的Unicode字符串。所以你使用纯ASCII文本编辑器的时候不需要担心了(不需要把你的字符串放入“Unicode标签”里)。

一个UTF-8编码的文件可以这样指示:

如果你执行或导入了包含非ASCII的Unicode字符串而没有在文件头部说明,那么你会在Python 2. 3得到一个DeprecationWaming,而在2. 5中这样做会导致语法错误。你可以在PEP 263中得到更多关于源文件编码的相关内容。

12.8.5 导入循环

实际上,在使用Python时,你会发现是能够导入循环的。如果你开发了大型的Python工程,那么你很可能会陷入这样的境地。

我们来看一个例子。假定我们的产品有一个很复杂的命令行接口(command-line interface, CLI) 。其中将会有超过一百万的命令,结果你就有了一个“超冗余处理器”(overly massive handler, OMH)子集。每加入一个新特性,将有1〜3条的新命令加入,用于支持新的特性。下边是我们的omh4cli.py脚本:

假定大多控制器都要用到这里的(其实是空的)工具函数。命令行接口的OMH都被封装在omh4cli()函数里。如果我们要添加一个新的命令,那么它会被调用。

现在这个模块不断地增长,一些聪明的工程师会决定把新命令放入到隔离的模块里,在原始模块中只提供访问新东西的钩子。这样,管理代码会变得更简单,如果在新加入内容中发现了bug,那么你就不必在一个几兆的Python文件里搜索。

在我们的例子中,有一个兴奋的经理要我们加入一个“非常好的特性”。我们将创建一个新的cli4vof.py脚本,而不是把新内容集成到omh4cli.py里:

前边已经提到,工具函数是每个命令必须的,而且由于不能把代码从主控制器复制出来,所以我们导入了主模块,在我们的控制器中添加对omh, omh4cli()的调用。

问题在于主控制器omh4cli会导入我们的cli4vof模块(获得新命令的函数),而cli4vof也会导入omh4cli(用于获得工具函数)。模块导入会失败,这是因为Python尝试导入一个先前没有完全导入的模块:

注意跟踪记录中显示的对cli4vof的循环导入。问题在于要想调用工具函数,cli4vof必须导入omh4cli。如果它不需要这样做,那么omh4cli将会成功导入cli4vof,程序正常执行。但在这里,omh4cli尝试导入cli4vof,而cli4vof也试着导入omh4cli。最后谁也不会完成导入工作,引发错误。这只是一个导入循环的例子。事实上实际应用中会出现更复杂的情况。

解决这个问题几乎总是移除其中一个导入语句。你经常会在模块的最后看到import语句。作为一个初学者,你只需要试着习惯它们,如果你以前遇到在模块底部的import语句,现在你知道是为什么了。在我们的例子中,我们不能把import omh4cli移到最后,因为调用cli4vof()的时候omh4cli()名字还没有被载入。

我们的解决方法只是把import语句移到cli4vof()函数内部:

这样,从omh4cli()导入cli4vof()模块会顺利完成,在omh4cli()被调用之前它会被正确导入。只有在执行到cli4vof.cli4vof()时候才会导入omh4cli模块。

12.8.6 模块执行

有很多方法可以执行一个Python模块:通过命令行或shell、execfile()、模块导入、解释器的-m选项等。这已经超出了本章的范围。你可以参考第14章,里边全面地介绍了这些特性。

12.9 相关模块

下边这些模块可能是你在处理Python模块导入时会用到的辅助模块。在这之中,modulefinder、pkgutil和zipimport是Python 2.3新增内容,distutils包在Python 2.0被引入。

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

  • modulefinder—该模块允许你查找Python脚本所使用的所有模块。你可以使用其中的ModuleFinder类或是把它作为一个脚本执行,提供你要分析的(另个)Python模块的文件名。

  • pkgutil—该模块提供了多种把Python包打包为一个“包”文件分发的方法。类似site模块,它使用*.pkg文件帮助定义包的路径,类似site模块使用的*.pth文件。

  • site—和*.pth文件配合使用,指定包加入Python路径的顺序,例如sys. path, PYTHONPATH。你不需要显式地导入它,因为Python导入时默认已经使用该模块。你可能需要使用-S开关在Python启动时关闭它。你也可以完成一些site相关的自定义操作,例如在路径导入完成后在另个地方尝试。

  • 你可以使用该模块导入ZIP归档文件中的模块。需要注意的是该功能已经“自动”开启,所以你不需要在任何应用中使用它。在这里我们提出它只是作为参考。

  • 该模块提供了对建立、安装、分发Python模块和包的支持。它还可以帮助建立使用C/C++完成的Python扩展。更多关于distutils的信息可以在Python文档里找到,参阅:

http://docs.python.org/dist/dist.html

http://docs.python.org/inst/inst.html

12.10 练习

12-1.路径搜索和搜索路径。路径搜索和搜索路径之间有什么不同?

12-2.导入属性。假设你的模块mymodule里有一个foo()函数。

(a)把这个函数导入到你的名称空间有哪两种方法?

(b)这两种方法导入后的名称空间有什么不同?

12-3.导入“import module”和“fromn module import*”有什么不同?

12-4.名称空间和变量作用域。名称空间和变量作用域有什么不同?

12-5.使用__import__()。

(a)使用__import__把一个模块导入到你的名称空间。你最后使用了什么样的语法?

(b)和上边相同,使用__import__()从指定模块导入特定的名字。

12-6.扩展导入。创建一个importAs()函数。这个函数可以把一个模块导入到你的名称空间,但使用你指定的名字,而不是原始名字。例如,调用newname=importAs(‘mymodule’)会导入mymodule,但模块和它的所有元素都通过新名称newname或newname.attr访问。这是Python 2. 0引入的扩展导入实现的功能。

12-7.导入钩子。研究PEP 302的导入钩子机制。实现你自己的导入机制,允许编码你的模块(encryption、bzip2、rot13等),这样解释器会自动解码它们并正确导入。你可以参看zip文件导入的实现(参阅第12.5.7节)。