python浓缩(12)模块

  • Python 模块如何把数据从模块中导入到编程环境中;

  • 包的相关概念;

  • 模块是用来组织 Python 代码的方法, 而包则是用来组织模块的;

  • 一些与模块有关的其他方面的问题;

12.1 什么是模块

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

12.2 模块和文件

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

12.2.1 模块名称空间

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

12.2.2 搜索路径和路径搜索

模块的导入需要一个"路径搜索"的过程。 即在文件系统"预定义区域"中查找 mymodule.py文件。 这些预定义区域就是Python 搜索路径的集合。

路径搜索和搜索路径是两个不同的概念:

  • 前者是指查找某个文件的操作;

  • 后者是去查找一组目录;

# 导入失败
>>> import xxx
Traceback (innermost last):
File "<interactive input>", line 1, in ?
ImportError: No module named xxx

可能的原因是模块不在搜索路径里, 从而导致了路径搜索的失败。默认搜索路径是在编译或是安装时指定的,可以在一个或两个地方修改:

(1)启动 Python 的 shell 或命令行的 PYTHONPATH 环境变量。 该变量的内容是一组用冒号分割的目录路径。 如果你想让解释器使用这个变量, 那么请确保在启动解释器或执行 Python 脚本前设置或修改了该变量。解释器启动之后访问这个搜索路径, 它会被保存在 sys 模块的 sys.path 变量里。不过它已经不是冒号分割的字符串, 而是包含每个独立路径的列表。下面是一个 Unix 机器搜索路径的样例。

>>> sys.path
['', '/usr/local/lib/python2.x/', '/usr/local/lib/ python2.x/plat-sunos5','/usr/local/lib/python2.x/ lib-tk', '/usr/local/lib/python2.x/lib-dynload', '/usr/local/lib/Python2.x/site-packages',]

可以对它进行修改。 要添加搜索路径 那么只需要调用列表的 append() 方法即可, eg:

sys.path.append('/home/wesc/py/lib')

修改完成后, 你就可以加载自己的模块了。 这个方法是把目录追加在搜索路径的尾部。 若有特殊需要, 应该使用列表的 insert() 方法操作 。 上面是在交互模式下修改 sys.path 的, 在脚本程序中也完全可以达到同样的目的:

>>> import sys
>>> import mymodule
Traceback (innermost last): File "<stdin>", line 1, in ?
ImportError: No module named mymodule
>>> sys.path.append('/home/wesc/py/lib')
>>> sys.path
['', '/usr/local/lib/python2.x/', '/usr/local/lib/python2.x/plat-sunos5', '/usr/local/lib/python2.x/lib-tk', '/usr/local/lib/python2.x/lib-dynload', '/usr/local/lib/python2.x/site-packages’,'/home/wesc/py/lib']
>>> import mymodule

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

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

12.3 名称空间

名称空间是名称(标识符)到对象的映射。 向名称空间添加名称的操作过程涉及到绑定标识符到指定对象的操作(以及给该对象的引用计数加 1 )。改变一个名字的绑定叫做重新绑定, 删除一个名字叫做解除绑定。

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

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

核心笔记: __builtins__ 和 __builtin__

__builtins__ 模块和 __builtin__ 模块不能混淆。 __builtins__ 模块包含内建名称空间中内建名字的集合。 其中大多数来自 __builtin__ 模块, 该模块包含内建函数, 异常以及其他属性。 在标准 Python 执行环境下,__builtins__ 包含 __builtin__ 的所有名字。 Python 曾经有一个限制执行模式, 允许你修改__builtins__ , 只保留来自 __builtin__ 的一部分, 创建一个沙盒(sandbox)环境。但是, 因为它有一定的安全缺陷, 而且修复它很困难, Python 已经不再支持限制执行模式。如果在执行期间调用了一个函数, 那么将创建出第三个名称空间, 即局部名称空间。 我们可以通过 globals() 和 locals() 内建函数判断出某一名字属于哪个名称空间。我们将在本章后面详细介绍这两个函数。

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

好了, 我们已经知道了什么是名称空间, 那么它与变量作用域有什么关系呢? 

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

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

python浓缩(12)模块_第1张图片

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

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

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

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

这个错误信息体现了先查找的名称空间是如何"遮蔽"其他后搜索的名称空间的。 这体现了名称覆盖的影响。 图 12 - 1 的灰盒子展示了遮蔽效应。

12.3.3 无限制的名称空间

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

def foo():
    pass
    foo.__doc__ = 'Oops, forgot to add doc str above!'
    foo.version = 0.2

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

mymodule.foo()
mymodule.version

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

class MyUltimatePythonStorageDevice(object):
    pass
    bag = MyUltimatePythonStorageDevice()
    bag.x = 100
    bag.y = 200
    bag.version = 0.1
    bag.completed = False

你可以把任何想要的东西放入一个名称空间里。 像这样使用一个类(实例)是很好的, 不管名字如何, 这个实例只是被用做一个名称空间。

随着学习的深入, 你会发现 OOP 是多么地有用, 比如在运行时临时(而且重要)变量的时候!

12.4 导入模块

12.4.1 语句

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

import module1
import module2[:
import moduleN

也可以在一行内导入多个模块:

import module1[, module2[,... moduleN]]

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

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

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

  • Python 标准库模块

  • Python 第三方模块

  • 应用程序自定义模块

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

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

12.4.2 from-import 语句

可以在模块里导入指定的模块属性。 也就是把指定名称导入到当前作用域。 使用from-import 语句:

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

12.4.3 多行导入

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

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 * 语句 (参kao第 12.5.3 一节的“核心风格”)。

真正的程序员应该使用 Python 的标准分组机制(圆括号)来创建更合理的多行导入语句:你可以在 PEP 328 找到更多关于多行导入的内容。

12.4.4 扩展的 import 语句(as)

有时候你导入的模块或是模块属性名称已经在你的程序中使用了, 或者你不想使用导入的名字。

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

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

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

import Tkinter
from cgi import FieldStorage

. . . 可以替换为 . . .

import Tkinter as tk
from cgi import FieldStorage as form

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 名字是这样被导入的:

from module import var

使用单个的 var 就可以访问它自身。 把 var 导入到名称空间后就再没必要引用模块了。

当然, 你也可以把指定模块的所有名称导入到当前名称空间里:

from module import *

核心风格: 限制使用 "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 = 'abc'
def show():
print 'foo from imptee:', foo
# impter.py #
from imptee import foo, show
show()
foo = 123
print 'foo from impter:', foo
show()

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

foo from imptee: abc
foo from impter: 123
foo from imptee: abc

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

# impter.py #

import imptee
imptee.show()
imptee.foo = 123
print 'foo from impter:', imptee.foo
imptee.show()

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

foo from imptee: abc
foo from impter: 123
foo from imptee: 123

12.5.5 关于 __future__

回首 Python 2.0 , 我们认识到了由于改进, 新特性, 以及当前特性增强, 某些变化会影响到当前功能。 所以为了让 Python 程序员为新事物做好准备, Python 实现了 __future__ 指令。使用 from-import 语句"导入"新特性, 用户可以尝试一下新特性或特性变化, 以便在特性固定下来的时候修改程序。 它的语法是:

from __future__ import new_feature

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

12.5.6 警告框架

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

首先是应用程序接口(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, or .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_hooks 。 sys.path_importer_cache 只是用来保存这些实例, 这样就只需要访问 path_hooks 一次。 最后, sys.meta_path 用来保存一列需要在查询 sys.path 之前访问的实例, 这些是为那些已经知道位置而不需要查找的模块准备的。 meta-path 已经有了指定模块或包的载入器对象的读取器。

12.6 模块内建函数

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

12.6.1 __import__()

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

__import__() 的语法是:

__import__(module_name[, globals[, locals[, fromlist]]])

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

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

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

sys = __import__('sys')

12.6.2 globals() 和 locals()

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

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

def foo():
print '\ncalling foo()...'
aString = 'bar'
anInt = 42
print "foo()'s globals:", globals().keys()
print "foo()'s locals:", locals().keys()
print "__main__'s globals:", globals().keys()
print "__main__'s locals:", locals().keys() foo()

我们只在这里访问了字典的键 , 因为它的值在这里没有影响(而且他们会让行变得更长更难懂)。

执行这个脚本, 我们得到如下的输出:

$ namespaces.py
__main__'s globals: ['__doc__', 'foo', '__name__', '__builtins__']
__main__'s locals: ['__doc__', 'foo', '__name__', '__builtins__']
calling foo()...
foo()'s globals: ['__doc__', 'foo', '__name__', '__builtins__']
foo()'s locals: ['anInt', 'aString']

12.6.3 reload()

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

reload(module)

使用 reload() 的标准: 模块必须是全部导入(不是使用 from-import), 而且它必须被成功导入;另外 reload() 函数的参数必须是模块自身而不是包含模块名的字符串;模块中的代码在导入时被执行, 但只执行一次. 以后执行 import 语句不会再次执行这些代码,只是绑定模块名称, 而 reload() 函数不同。

12.7 包

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

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

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

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

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

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

12.7.1 目录结构

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

  python浓缩(12)模块_第2张图片

python浓缩(12)模块_第3张图片

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

import Phone.Mobile.Analog
Phone.Mobile.Analog.dial()

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

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

from Phone import Mobile
Mobile.Analog.dial('555-1212')

(2)引用更多的子包:

from Phone.Mobile import Analog
Analog.dial('555-1212')

(3)一直沿子包的树状结构导入:

from Phone.Mobile.Analog import dial
dial('555-1212')

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

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

12.7.2 使用 from-import 导入包

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

from package.module import *

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

12.7.3 绝对导入

很多情况下导入子包会导致和真正的标准库模块发生(事实上是它们的名字)冲突。包模块会把名字相同的标准库模块隐藏掉, 因为它首先在包内执行相对导入, 隐藏掉标准库模块。为此, 所有的导入现在都被认为是绝对的, 也就是说这些名字必须通过 Python 路径(sys.path 或是 PYTHONPATH )来访问。

这个决定的基本原理是子包也可以通过 sys.path 访问, 例如 import Phone.Mobile.Analog 。

在这个变化之前, 从 Mobile 子包内模块中导入 Analog 是合理的。作为一个折中方an, Python 允许通过在模块或包名称前置句点实现相对导入。 更多信息请参阅第 12.7.4 节。

从 Python 2.7 开始, 绝对导入特性将成为默认功能。 ( 从 Python 2.5 开始, 你可以从__future__ 导入 absolute_import , 体验这个功能。)

12.7.4 相对导入

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

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

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

import Analog

from Analog import dial

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

from Phone.Mobile.Analog import dial

from .Analog import dial

from ..common_util import setup

from ..Fax import G3.dial.

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

12.8 模块的其他特性

12.8.1 自动载入的模块

当 Python 解释器在标准模式下启动时, 一些模块会被解释器自动导入, 用于系统相关操作。

唯一一个影响你的是 __builtin__ 模块, 它会正常地被载入, 这和 __builtins__ 模块相同。

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

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

>>> import sys
>>> sys.modules.keys()
['os.path', 'os', 'exceptions', '__main__', 'ntpath',
'strop', 'nt', 'sys', '__builtin__', 'site',
'signal', 'UserDict', 'string', 'stat']

Unix 下载入的模块很类似:

>>> import sys
>>> sys.modules.keys()
['os.path', 'os', 'readline', 'exceptions',
'__main__', 'posix', 'sys', '__builtin__', 'site',
'signal', 'UserDict', 'posixpath', 'stat']

12.8.2 阻止属性导入

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

12.8.3 不区分大小的导入

有一些操作系统的文件系统是不区分大小写的。 Python 2.1 前, Python 尝试在不同平台下导入模块时候"做正确的事情", 但随着 MacOS X 和 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 编码的文件可以这样指示:

#!/usr/bin/env python

# -*- coding: UTF-8 -*-

如果你执行或导入了包含非 ASCII 的 Unicode 字符串而没有在文件头部说明, 那么你会在Python 2.3 得到一个 DeprecationWarning , 而在 2.5 中这样做会导致语法错误。你可以在 PEP

263 中得到更多关于源文件编码的相关内容。

12.8.5 导入循环

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

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

from cli4vof import cli4vof
# command line interface utility function
def cli_util():
pass
# overly massive handlers for the command line interface
def omh4cli():
:
cli4vof()
:
omh4cli()

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

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

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

import omh4cli
# command-line interface for a very outstanding feature
def cli4vof():
omh4cli.cli_util()

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

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

$ python omh4cli.py
Traceback (most recent call last):
File "omh4cli.py", line 3, in ? from cli4vof import cli4vof
File "/usr/prod/cli4vof.py", line 3, in ?
import omh4cli
File "/usr/prod/omh4cli.py", line 3, in ?
from cli4vof import cli4vof
ImportError: cannot import name cli4vof

注意跟踪返回消息中显示的对 cli4vof 的循环导入。 问题在于要想调用工具函数, cli4vof 必须导入 omh4cli 。 如果它不需要这样做, 那么 omh4cli 将会成功导入 cli4vof , 程序正常执行。

但在这里, omh4cli 尝试导入 cli4vof , 而 cli4vof 也试着导入 omh4cli 。 最后谁也不会完成导入工作, 引发错误。 这只是一个导入循环的例子。 事实上实际应用中会出现更复杂的情况。

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

$ python omh4cli.py
Traceback (most recent call last): File "omh4cli.py", line 3, in ? from cli4vof import
cli4vof
File "/usr/prod/cli4vof.py", line 7, in ?
import omh4cli
File "/usr/prod/omh4cli.py", line 13, in ?
omh4cli()
File "/usr/prod/omh4cli.py", line 11, in omh4cli cli4vof()
File "/usr/prod/cli4vof.py", line 5, in cli4vof omh4cli.cli_util()
NameError: global name 'omh4cli' is not defined

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

def cli4vof():
import omh4cli
omh4cli.cli_util()

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

12.8.5 模块执行

有很多方法可以执行一个 Python 模块: 通过命令行或 shell , execfile() , 模块导入, 解释器的 -m 选项, 等等。这已经超出了本章的范围。 你可以参kao 第 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 相关的自定义操作,例如在路径导入完成后在另个地方尝试。

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

  • distutils - 该模块提供了对建立、 安装、分发 Python 模块和包的支持。 它还可以帮助建立使用 C/C++ 完成的 Python 扩展。

更多关于 distutils 的信息可以在 Python 文档里找到, 参阅:

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

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

你可能感兴趣的:(python浓缩(12)模块)