Python Tutorial(六):模块

如果你从 Python 解释器中退出,并且再次进入,你会发现你以前定义的函数和变量都已经丢失了。所以,如果你想写一个在某种程度上更长的程序,使用一个文本编辑器来准备解释器的输入会使情况有所好转,并且使用文件代替输入来执行它。这就是被熟知的创建一个脚本。随着你的程序变的更长时,你或许想把它分割成几个文件,这样便于维护。你或许想在几个程序里面使用一个常用的函数,而不用把它的定义拷贝到每一个程序里面。

为了支持这些,Python 有一个方式来把定义放到一个文件里,并且在一个脚本里或解释器的一个交互实例中使用它们。这样的一个文件叫做模块,一个模块里面的定义可以被导入到其它模块里面或主模块里面(在顶级以计算器方式执行的脚本里面访问的变量集合)。

一个模块就是一个包含 Python 定义和语句的文件。文件名就是模块名加上后缀.py。在一个模块里面,模块的名字(一个字符串)可以使用全局变量 __name__ 获得。例如,使用你喜爱的文本编辑器在当前的目录里面来创建一个 fibo.py 的文件,包含一些内容:

然后进入 Python 解释器,使用下面的命令导入这个模块:

这样做并没有把直接定义在 fibo 中的函数名称写入到当前符号表里,只是把模块 fibo 的名字写到了那里。

可以使用模块名称来访问函数:

如果你打算经常使用一个函数,你可以把它赋给一个本地的名称:

6.1 更多有关模块

模块可以包含可执行语句和函数定义。这些语句是打算用来初始化模块的。这些语句只有在模块第一次被导入到其它地方的时候会被执行。

每一个模块有它自己的私有符号表,它被定义在模块里的所有函数用作全局符号表。因此,一个模块的设计者可以在模块里使用全局变量而不用担心会和用户的全局变量产出意外的冲突。换句话来说,如果你知道你正在干什么,你可以触及模块里面的全局变量,使用和以前引用模块函数相同的标记法,modname.itemname。

模块可以引入其它模块。把所有的导入语句放到模块的开始是一种习惯,但不要求非得这样做。被导入模块的名称被放入到引入模块的全局符号表里。

有一个导入语句的变体可以把一个模块里的名称直接导入到另一个模块的符号表里面。例如:

这样不会引入模块的名称,这些导入的内容被放到本地符号表(所以在示例中,fibo 没有定义)。

甚至由一个变体可以引入一个模块定义的所有名称:

这将引入所有除了以下划线开头的名称。大多数情况,Python 程序员不使用这样的功能,因为它引入了一个不知道的名称集合到解释器里,可能会隐藏掉你已经定义的一些东西。

注意,一般来说,从一个模块或包里面引入所有的名称在实践中是不被赞成的,因为它经常造成可读性差的代码。然而,在交互式会话里可以使用它来节省输入。

注意,出于效率的原因,在一个会话里,每个模块只会被引入一次。如果你改变了你的模块,你必须重新启动解释器,或者如果你想交互测试的仅仅是一个模块,可以使用 imp.reload(),例如:import imp; imp.reload(modulename)。

6.1.1 把模块作为脚本执行

当你以下面方式执行 Python 模块时:

模块里面的代码将会被执行,就好像你引入它一样,但是会把 __name__ 设置为 __main__。那就意味着把这些代码添加到模块的尾部:

你可以把文件当作脚本和可导入模块来使用,因为解析命令行的代码只有在模块作为主文件执行时才运行:

如果模块被导入,代码不执行:

这通常用来要么给模块提供一个方便的用户界面,或为了测试(把模块作为脚本运行,来执行一个测试单元)。

6.1.2 模块搜索路径

当一个名为 spam 的模块被导入时,解释器首先搜索具有这个名称的内建模块。如果没有找到,然后在 sys.path 这个变量指定的目录列表里面搜索一个名为 spam.py 的文件。sys.path 从这些地方被初始化:

  • 包含输入脚本的目录(或当前目录)。
  • PYTHONPATH(一个目录名称的列表,和 shell 变量 PATH 的语法一样)。
  • 取决于安装时的默认值。

在初始化后,Python 程序可以修改 sys.path。包含正在运行的脚本的目录被放到了搜索路径的开始处,在标准库路径前面。这意味着那个目录里面的脚本会被加载而不是库目录里面的同名模块。这是一个错误除非替换被打算。

6.1.3 “编译”Python 文件

作为一个对那些使用了许多标准模块的短的程序的启动时间的重要加速,如果在发现 spam.py 的目录里面已经存在一个叫 spam.pyc 的文件,这被假定包含模块 spam 的一个已按字节编译的版本。用来创建 spam.pyc 的 spam.py 的版本修改时间被记录在 spam.pyc 里面,并且.pyc 文件会被忽略如果这两个时间不匹配。

通常,你不需要做任何事情来创建 spam.pyc 文件。无论什么时候 spam.py 文件被成功的编译,将会进行一次把编译好的版本写入到 spam.pyc 的尝试。如果这次尝试失败的话也不算是错误;如果由于任何原因这个文件没有被写完,结果 spam.pyc 文件被认为是非法的,并且在以后忽略它。spam.pyc 文件的内容是平台独立的,所以一个 Python 模块目录可以被不同架构的机器共享。

一些对专家的提示:

  • 当 Python 的解释器以 -O 的标志被调用时,将产生优化的代码并存储在.pyo 文件里面。优化器现在帮助不了太多;它仅仅移除 assert 语句。当 -O 使用时,所有的字节码都被优化;.pyc 文件被忽略和.py 文件被编译成优化的字节码。
  • 传递两个 -O 标志到 Python 的解释器(-OO)将使字节码编译器来执行优化,这种优化在一些特殊的情况下会导致程序出故障。当前只有文档字符串从字节码中被移除,产生更加压缩的.pyo 文件。因为一些程序或许依赖使这些可用,你应该只有在你知道自己在做什么时再使用这个选项。
  • 一个程序从.pyc 或.pyo 文件读入并不比从.py 文件读入运行的快很多;关于.pyc 或.pyo 文件惟一快的事情是它们被加载的速度。
  • 在命令行使用脚本名称运行脚本时,脚本的字节码从不写入.pyc 或.pyo 文件。因此,把一个脚本的大多数代码移到一个模块里面,产生一个较小的启动脚本并引入那个模块,可以减少一个脚本的启动时间。也可以在命令行直接命名一个.pyc 或.pyo 文件。
  • 在同一个模块里面有一个 spam.pyc 文件(或 spam.pyo 当 -O 使用时)而没有一个 spam.py 文件也是可能的。这可以用于以对逆向工程师有一定难度的形式来发布 Python 代码库。
  • compileall 模块能为一个目录里面的所有模块创建.pyc 文件(或.pyo 文件当 -O 使用时)。

6.2 标准模块

Python 有一个标准模块的库,在一个单独的文档中描述,Python 库参考。一些模块被内建到解释器中;它们提供的访问操作不是语言的核心部分但是仍然被内建其中,要么是为了效率或提供访问操作系统原始的内容如系统调用。这些模块集是一个配置选项,它们也取决于底层的平台。例如,winreg 模块只有 Windows 系统提供。一个特殊的模块应受到一些关注:sys,它被内建到每一个 Python 解释器中。变量 sys.ps1 和 sys.ps2 定义了用作主要和第二命令提示符的字符串:

这两个变量只定义在解释器处于交互模式时。

变量 sys.path 是一个字符串列表,决定了解释器的模块搜索路径。它使用环境变量 PYTHONPATH 的值进行初始化为一个默认的路径,或从一个内建默认值如果 PYTHONPATH 没有设置。你可以使用标准的列表操作修改它:

6.3 dir() 函数

内建的函数 dir() 用来找出一个模块都定义了那些名称。它返回一个已排序的字符串列表:

没有参数的话,dir() 列出当前你已经定义的名称:

注意,它列出所有类型的名称:变量,模块,函数等。

dir() 并不列出内建的函数和变量的名称。如果你想列出那些名称,它们都定义在标准的模块 buildins 里面:

6.4 包

包是使用点模块名称来构建 Python 模块命名空间的一种方式。例如,模块名称 A.B 表明一个名称为 B 的子模块在一个名称为 A 的包里面。就像模块的使用使不同模块的作者不用再担心彼此的全局变量名称,点模块名称的使用使多模块包(像 NumPy 或 Python 镜像库)的作者不用再担心彼此的模块名称。

假定你想设计一些模块来统一的处理声音文件和声音数据。有许多不同格式的声音文件,所以或许你需要创建和维护一个持续增长的模块集合在多种不同格式的文件之间进行转换。或许也想在声音数据上执行多种不同的操作,所以除此之外你将写一个永远没有头的模块流来执行这些操作。这是一个可能的包结构:

当引入这个包,Python 通过 sys.path 上的目录来搜索查找包子目录。

__init__.py 文件是必须的,它使 Python 把这些目录作为包含包来对待;这样就阻止了稍后发生在模块搜索路径上的一个具有普通名称的目录无意中隐藏了合法模块。在最简单的情况下,__init__.py 可能仅是一个空的文件,但是它也能为包执行初始化代码或设置 __all__ 变量,稍后描述。

包的用户可以从包里面单个的导入模块,例如:

这加载子模块 sound.effects.echo。必须使用全名来引用它:

一个导入子模块的可选方式是:

这也加载子模块 echo,并且使它不带包前缀也可以使用,所以它能按如下方式使用:

另一种变体是用来直接导入期望的函数或变量:

加载子模块 echo,并且使它的函数 echofilter() 直接可以使用:

注意,当使用 from package import item 时,item 要么是包的子模块(或子包), 或者是包里定义的一些其它名称,像函数,类或者变量。import 语句首先测试 item 是否定义在包里;如果没有,就假定它是一个模块并且尝试去加载它。如果没有成功的找到它,一个 ImportError 异常被激发。

反之,当使用像 import item.subitem.subsubitem 这样的语法时,除了最后一项的其它项都必须是一个包;最后一项可以是一个模块或一个包,但不能是定义在前一项里面的一个类或函数或变量。

6.4.1 从一个包里导入 *

当用户写下 from sound.effects import * 是会发生什么?理论上讲,一个人希望这以某种方式走出文件系统,找出哪些子模出现在块包里存,并且全部导入它们。这可能花费较长的时间,并且正在导入的子模块可能有不希望的副作用,这个副作用应该只有在这个子模块被显式导入时才发生。

唯一的解决方案就是包的作者提供一个显示的包的索引。import 语句使用下面的约定:如果一个包的 __init__.py 代码定义了一个名为 __all__ 的列表,它被认为是应该导入的模块名称的列表当遇到 from package import * 时。这取决于包的作者来保持这个列表是最新的当一个包的新的版本被发布时。包的作者们也可以决定不支持它,如果他们没有看到 import * from 他们的包的使用。例如,文件 sounds/effects/__init__.py 可能包含下面的代码:

这将意味着 from sound.effects import * 将导入 sound 包的三个命名的子模块。

如果 __all__ 没有定义,语句 from sound.effects import * 并不从 sound.effects 包里导入所有的子模块到当前的命名空间里;它仅仅确认包 sound.effects 已经被导入(可能的运行 __init__.py 里面的任何初始化代码)并且导入包里定义的任何名称。这包含通过 __init__.py 定义的任何名称(和显式加载的子模块)。这也包括通过上一个 import 语句被显式加载的包的任何子模块。考虑下面的代码:

在这个例子里,当 from...import 语句被执行时,模块 echo 和 surround 被导入到当前的命名空间,因为它们定义在 sound.effects 包里。(当 __all__ 被定义时这也起作用。)

当使用 import * 时,虽然遵从确定的模式,确定的模块被设计为只输出名称,在生产代码里它仍然被认为是坏的实践。

记住,使用 from Package import specific_submodule 没有错误。事实上,这是建议的写法,除非正在导入的模块需要使用来自不同包的具有相同名称的子模块。

6.4.2 内置包的引用

当包被结构化到子包里(就像例子中的 sound 包),你可以使用绝对的 imports 来引用兄弟包的模块。例如,如果模块 sound.filters.vocoder 需要使用包 sound.effects 里面的 echo 模块,可以使用 from sound.effects import echo。

你也可以写相对的 imports,使用 import 语句的 from module import name 形式。这些 imports 使用前导点(句点儿)来指示在相对 import 里面包含的当前的和父亲的包。从 surround 模块,你可以这样使用:

注意,相对 imports 是基于当前模块的名称。因为主模块的名称总是 "__main__",打算用作 Python 应用程序的主模块的那些模块必须总是使用绝对 imports。

多个目录里的包

包多支持一个特别的属性,__path__。它被初始化为一个包含包的 __init__.py 文件的目录的名称的列表在那个文件里的代码被执行之前。这个变量可以被改变,这样做影响将来对包里的模块和子包的搜索。

当然,这个特性也不常用,它可以被用于扩展一个包里的模块集合。

本文是对官方网站内容的翻译,原文地址:http://docs.python.org/3/tutorial/modules.html