6 模块

如果你退出 python 的解释器然后重新进入的话,你之前定义的函数和变量都会丢失。因此,如果你想写一个有点长的程序,你最好事先准备一个文本编辑器,然后在解释器直接运行这个文件。这是就是所谓的创建 脚本。随着你的程序变得更长,你也许要把它分割成若干的文件,这样维护起来更方便。你也可以用一个你已经在若干程序中写好的随时可用的函数,而不用把这个函数的定义复制到每一个程序中。

Python 有一种把函数定义在一个文件的方法,你可以在脚本中,或者在解释器的交互实例中使用这个文件。这样的一个文件就叫做 模块。在一个模块中的定义可以 导出 到其他的模块中,或者 main 模块中 (一个变量的集合,你可以在脚本中得到这些变量,这些脚本在计算器模式下高层次被执行)。

一个模块文件包含了 Python 的定义和声明。文件名就是模块名加上 .py 后缀。在一个模块中,模块的名字 (作为字符串) 可以作为全局变量 __name__而得到。 在当前目录下, 用下面的内容创建一个名为 fibo.py 的文件:

  1. # Fibonacci numbers module
  2. def fib(n): # write Fibonacci series up to n
  3. a, b = 0, 1
  4. while b < n:
  5. print(b, end=' ')
  6. a, b = b, a+b
  7. print()
  8. def fib2(n): # return Fibonacci series up to n
  9. result = []
  10. a, b = 0, 1
  11. while b < n:
  12. result.append(b)
  13. a, b = b, a+b
  14. return result

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

  1. >>> import fibo

在当前的符号表中,不用直接输入在 fibo 中定义的函数名字。它仅仅在里面输入了模块名 fibo。使用模块名你就可以使用这些函数了:

  1. >>> fibo.fib(1000)
  2. 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
  3. >>> fibo.fib2(100)
  4. [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
  5. >>> fibo.__name__
  6. 'fibo'

如果你经常使用一个函数,你可以把这个函数赋值给一个本地的变量名:

  1. >>> fib = fibo.fib
  2. >>> fib(500)
  3. 1 1 2 3 5 8 13 21 34 55 89 144 233 377

6.1 模块的更多介绍

一个模块可以像函数定义那样包含可执行的语句。这些语句用来初始化模块。他们只有在 第一次 在导入 (import) 语句遇到模块名的时候才会执行。(如果文件是作为一个脚本执行的,他们也会运行。)

注释 实际上,函数的定义也是 被“执行” 的语句。模块层面的函数定义的执行在模块的全局符号表中进入函数名。

每一个模块都有属于自己的私有符号表,一般用来作为模块中定义的所有函数的全局符号表。因此,一个模块的作者可以使用全局变量儿不用担心与用户的全局变量意外的冲突。另一方面,如果你知道你正在做什么,你可以使用相同的用于指代函数的符号来得到模块的全局变量,modename.itemname

模块可以导入其他模块。这很常见,但是并不要求把所有的 import 语句放在一个模块的最开头 (或者脚本,就此而言)。被导入的模块名字放在导入其他模块的模块的全局符号表中。

有一个 import 语句的一种变体,它从一个模块中直接把模块名导入到模块的符号表中。举个例子:

  1. >>> from fibo import fib, fib2
  2. >>> fib(500)
  3. 1 1 2 3 5 8 13 21 34 55 89 144 233 377

这并没有引入从本地符号表中导入的模块名 (所以在这个例子中,fibo 是未定义的)。

甚至还有一种变体,用它导入所有模块中定义的名字:

  1. >>> from fibo import *
  2. >>> fib(500)
  3. 1 1 2 3 5 8 13 21 34 55 89 144 233 377

这就导入了所有除了以下划线 (_) 开头的名字。在大多数情况下,Python 程序员不用这种用法,因为它往解释器中引入了一系列未知的名字,有可能会隐藏一些你已经定义过的东西。

注意,在一般的实际应用中,从一个模块或者包中导入 * 是不赞成使用的,因为这常常会导致可读性很差的代码。然而,在交互会话可以这么使用来减少输入。

注意:因为效率性的原因,每一个模块在每一个解释器会话中只导入一次。因此,如果你想改变你的模块,你就必须要重启解释器,否则,如果它仅仅是一个你想用来交互性的测试的一个模块,就使用 importlib.reload()。举个例子:import importlib; importlib.reload(modulename)

6.1.1 把模块当作脚本执行

当你用下面的语句运行 Python 模块的时候

  1. python fibo.py <arguments>

正如你导入这个模块一样,模块中的代码将会被执行,但是会把 __name__ 设置为 "__main__"。这就意味这,你可以通过在模块的结尾加上如下代码:

  1. if __name__ == "__main__":
  2. import sys
  3. fib(int(sys.argv[1]))

就可以让这个文件像可导入的模块一样,作为一个脚本来使用,因为这个转换成命令行代码只有当模块作为 “main” 文件被执行的时候才会运行:

  1. $ python fibo.py 50
  2. 1 1 2 3 5 8 13 21 34

如果模块是被导入的,代码就不会执行:

  1. >>> import fibo
  2. >>>

这常常用在提供给模块一个方便的用户接口或者用于检测目的 (作为脚本运行模块会执行测试程序)。

6.1.2 模块搜索路径

当一个命名为 spam 的模块在被导入的时候,解释器会首先使用这个名字搜索内建模块。如果没有找到,就会在变量 sys.path 给出的目录下搜索名为 spam.py 的文件。 sys.path 从一下位置初始化:

  • 包含输入脚本的目录 (当没有指定文件的时候就是当前目录)

  • PYTHONPATH (一个目录名列表,和 shell 变量 PATH 使用同样的语法)

  • 默认的安装依赖

注意 在支持符号链接的文件系统下,包含输入脚本的目录在符号链接被跟踪之后才计算。也就是说,包含符号链接的目录不会被加入模块搜索路径中。

初始化之后,Python 程序可以改变 sys.path。被执行的包含脚本的目录放置在搜索路径的开头,在标准库路径之前。这就意味着在这个目录中的脚本将会被加载,而在库目录中同名的模块不会加载。除非这个替代是有意的,不然会有错误。更多信息,参见 标准模块

6.1.3 “编译的” Python 文件

为了加快模块的加载,Python 在名为 module.version.pyc 的目录下缓存了每个模块的版本,在这个目录下,版本编码编译文件的格式,它通常包含了 Python 的版本号。例如,在 CPython 3.3 版本中,spam.py 编译的版本会被缓存为 __pycache__/spam.cpython-33.pyc。这种命名约定允许来自不同发布和不同版本的 Python 的模块可以共存。

Python 会检查源码的修改日期和编译版本来看它是否过期了,需不需要重新编译。这完全是一个自动的过程。而且,编译的模块是独立与平台的,所以相同的库可以在不同体系结构的系统中共享。

Python 在两种情况下不会检查缓存。第一,从命令行中加载的模块总会被编译,而且其结果不会被保存。第二,如果没有源模块,就不会检查缓存。为了支持无源 (只能被编译) 的分配,被编译的模块必须在源目录中,而且不能有源模块。

专家建议:

  • 你可以在命令行使用 -O 或者 -OO 选项来缩小编译模块的文件大小。-O 选项会删除断言语句,-OO 会同时删除断言语句和 __doc__ 字符串。因为一些程序可能会依赖这些变量,所以你应该仅仅在你知道你在做什么的时候使用。“优化的”模块有一个 opt- 的标签,而且一般更小。未来的发行版本可能会改变优化的效果。

  • 当一个项目从一个 .pyc 文件中读取的时候不会比从 .py 文件中读取运行的更快。唯一一个 .pyc 更快的地方是他们加载的速度。

  • compileall 模块可以为一个目录下所有的模块创建 .pyc 文件。

  • 这个过程有更多细节,包括判断流程图。见 PEP 3147。