6.1. 深入模块
最后更新于:2022-04-01 00:48:10
除了包含函数定义外,模块也可以包含可执行语句。这些语句一般用来初始化模块。他们仅在 _第一次_ 被导入的地方执行一次。[[1]](http://www.pythondoc.com/pythontutorial3/modules.html#id11)
每个模块都有自己私有的符号表,被模块内所有的函数定义作为全局符号表使用。因此,模块的作者可以在模块内部使用全局变量,而无需担心它与某个用户的全局变量意外冲突。从另一个方面讲,如果你确切的知道自己在做什么,你可以使用引用模块函数的表示法访问模块的全局变量,modname.itemname。
模块可以导入其他的模块。一个(好的)习惯是将所有的 import 语句放在模块的开始(或者是脚本),这并非强制。被导入的模块名会放入当前模块的全局符号表中。
import 语句的一个变体直接从被导入的模块中导入命名到本模块的语义表中。例如:
~~~
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
~~~
这样不会从局域语义表中导入模块名(如上所示, fibo 没有定义)。
甚至有种方式可以导入模块中的所有定义:
~~~
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
~~~
这样可以导入所有除了以下划线( _ )开头的命名。
需要注意的是在实践中往往不鼓励从一个模块或包中使用 * 导入所有,因为这样会让代码变得很难读。不过,在交互式会话中这样用很方便省力。
Note
出于性能考虑,每个模块在每个解释器会话中只导入一遍。因此,如果你修改了你的模块,需要重启解释器;或者,如果你就是想交互式的测试这么一个模块,可以用 reload() 重新加载,例如 reload(modulename)。
### 6.1.1\. 作为脚本来执行模块
当你使用以下方式运行 Python 模块时,模块中的代码便会被执行:
python fibo.py
模块中的代码会被执行,就像导入它一样,不过此时 __name__ 被设置为 "__main__"。这相当于,如果你在模块后加入如下代码:
~~~
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
~~~
就可以让此文件像作为模块导入时一样作为脚本执行。此代码只有在模块作为 “main” 文件执行时才被调用:
~~~
$ python fibo.py 50
1 1 2 3 5 8 13 21 34
~~~
如果模块被导入,不会执行这段代码:
~~~
>>> import fibo
>>>
~~~
这通常用来为模块提供一个便于测试的用户接口(将模块作为脚本执行测试需求)。
### 6.1.2\. 模块的搜索路径
导入一个叫 spam 的模块时,解释器先在当前目录中搜索名为 spam.py 的文件。如果没有找到的话,接着会到 sys.path 变量中给出的目录列表中查找。 sys.path 变量的初始值来自如下:
* 输入脚本的目录(当前目录)。
* 环境变量 PYTHONPATH 表示的目录列表中搜索 (这和 shell 变量 PATH 具有一样的语法,即一系列目录名的列表)。
* Python 默认安装路径中搜索。
实际上,解释器由 sys.path 变量指定的路径目录搜索模块,该变量初始化时默认包含了输入脚本(或者当前目录), PYTHONPATH 和安装目录。这样就允许 Python 程序了解如何修改或替换模块搜索目录。需要注意的是由于这些目录中包含有搜索路径中运行的脚本,所以这些脚本不应该和标准模块重名,否则在导入模块时 Python 会尝试把这些脚本当作模块来加载。这通常会引发错误。请参见 [_标准模块_](http://www.pythondoc.com/pythontutorial3/modules.html#tut-standardmodules) 以了解更多的信息。
### 6.1.3\. “编译的” Python 文件
对于引用了大量标准模块的短程序,有一个提高启动速度的重要方法,如果在 spam.py 所在的目录下存在一个名为 spam.pyc 的文件,它会被视为 spam 模块的预“编译”( byte-compiled ,二进制编译)版本。用于创建 spam.pyc 的这一版 spam.py 的修改时间记录在 spam.pyc 文件中,如果两者不匹配,.pyc 文件就被忽略。
通常你不需要为创建 spam.pyc 文件做任何工作。一旦 spam.py 成功编译,就会尝试生成对应版本的 spam.pyc 。如果有任何原因导致写入不成功,生成的 spam.pyc 文件就会视为无效,随后即被忽略。 spam.pyc 文件的内容是平台独立的,所以 Python 模块目录可以在不同架构的机器之间共享。
部分高级技巧:
* 以 _-O_ 参数调用 Python 解释器时,会生成优化代码并保存在 .pyo 文件中。现在的优化器没有太多帮助;它只是删除了断言( assert )语句。使用 _-O_ 参数, _所有_ 的字节码( _bytecode_ )都会被优化;.pyc 文件被忽略,.py 文件被编译为优化代码。
* 向 Python 解释器传递两个 _-O_ 参数( _-OO_ )会执行完全优化的二进制优化编译,这偶尔会生成错误的程序。现在的优化器,只是从字节码中删除了 __doc__ 符串,生成更为紧凑的 .pyo 文件。因为某些程序依赖于这些变量的可用性,你应该只在确定无误的场合使用这一选项。
* 来自 .pyc 文件或 .pyo 文件中的程序不会比来自 .py 文件的运行更快;.pyc 或 .pyo 文件只是在它们加载的时候更快一些。
* 通过脚本名在命令行运行脚本时,不会将为该脚本创建的二进制代码写入 .pyc 或 .pyo 文件。当然,把脚本的主要代码移进一个模块里,然后用一个小的启动脚本导入这个模块,就可以提高脚本的启动速度。也可以直接在命令行中指定一个 .pyc 或 .pyo 文件。
* 对于同一个模块(这里指例程 spam.py --译者),可以只有 spam.pyc 文件(或者 spam.pyc,在使用 _-O_ 参数时)而没有 spam.py 文件。这样可以打包发布比较难于逆向工程的 Python 代码库。
* compileall 模块可以为指定目录中的所有模块创建 .pyc 文件(或者使用 _-O_ 参数创建 .pyo 文件)。