如果你从 Python 解释器退出然后再进入它,你所做的定义(函数和变量)都会消失。因此,如果你想写某些更长的程序,你最好使用一个文本编辑器来为解释器准备输入,然后以这个文件作为输入来运行程序。这也被称为创建一个 脚本。当你的程序变得更长,你也许会想将它分成几个文件方便管理。你也许还想在几个程序中直接使用函数而不用在每个程序中拷贝函数定义。

为了支持这种特性,Python 可以把定义放入一个文件中然后在一个脚本或交互式解释器实例中使用它。这个文件被叫做 模块 (module),模块中的定义可以通过 导入 进入到其他模块或者 模块(你在顶层和计算器模式下执行的脚本中可以访问的变量集合)。

一个模块是一个包含 Python 定义和声明的文件。文件是模块名加上 .py 后缀。在一个模块中,模块名(字符串类型)可以通过全局变量 __name__ 获取。例如,使用你最喜欢的文本编辑器在当前目录下创建一个叫 fibo.py 的文件,这个文件包含以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 斐波那契数模块

def fib(n): # 打印斐波那契数直到 n
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()

def fib2(n): # 返回到 n 的斐波那契数
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result

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

1
>>> import fibo

这样做不会直接在当前环境中导入 fibo 中定义的函数名,只会导入名为 fibo 的模块。使用模块名可以获取模块中定义的函数:

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

如果你打算经常使用一个函数,你可以把它赋值给一个局部变量

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

模块进阶

一个模块可以包含可执行声明包括函数定义。这些声明被用于初始化模块。它们只在模块被 第一 次导入时执行。 [1](如果文件被作为脚本运行它们也会被执行)

每个模块都有其私有的符号表,模块中定义的所有函数将这个符号表作为全局符号表。因此,一个模块的作者可以在模块中使用全局变量而无需担心与其他模块的全部变量冲突。另一方面,如果你知道你在干什么,你同样可以使用 模块.变量 的方式来获取一个模块的全局变量。

模块可以导入其他模块。将所有 import 语句放在模块(或者脚本,如果这个问题重要的话)的开头不是必须的,但习惯如此。被导入的模块名被放置于当前模块的全局符号表中。

import 声明的一种变体可以把一个模块中的变量直接导入当前模块的符号表中。例如:

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

这样做不会把模块名引入本地符号表中(因此上面的例子里,fibo 没有被定义)

还有一种导入声明的变体可以导入一个模块中定义的所有变量:

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

这会导入模块中除了以下划线(_)开头的所有名称。大多数情况下 Python 程序员不使用这个机制,因为它会为解释器引入一系列位置未知变量,从而有可能覆盖你已经定义的某些变量。

请注意,一般来说,使用 import * 导入模块或包是不受欢迎的,因为这通常会降低代码可读性。但在使用交互型解释器为了减少打字而使用它是允许的。

如果模块名后紧跟 as, 那么 as 后的变量名会与被导入的模块名绑定。

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

这与 import fibo 的方式导入相同,除了模块现在被命名为 fib 而不是 fibo

使用 from 时可以使用这个机制达到相同的效果:

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

注意

由于性能原因,每个模块在每个解释器会话中只会被导入一次。因此,如果你改变了你的模块,你必须重启解释器 – 或者你只想交互式地测试一个模块,你可以使用 importlib.reload(),例如,import importlib;importlib.reload(modulename)

将可执行模块当做脚本

当你这样使用一个 Python 模块

1
python fibo.py <arguments>

模块中的代码会被执行,就如你导入这个模块一样,除了 __name__ 变量被赋值为 "__main__"。这意味着在模块的末尾添加下面的代码:

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

你可以让一个文件当成脚本来使用同时也可以将其作为一个可导入模块,因为代码会解析命令行,并只在模块作为 “main” 文件时才会执行。

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

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

1
2
>>> import fibo
>>>

这经常被用于提供模块的用户接口或测试(以脚本的形式运行模块来执行某个测试集)

模块搜索路径

当一个名为 spam 的模块被导入时,解释器首先寻找同名的内建模块。如果没有发现同名内建模块,解释器会根据 sys.path 提供的一系列路径下寻找名为 spam.py 的文件。sys.path 根据下面这些位置进行初始化:

  • 包含输入脚本的目录(如果没有指明文件则为当前目录)
  • PYTHONPATH 一个目录的列表,语法与 shell 的 PATH 变量相同。
  • 安装依赖默认路径

注意

在支持符号链接的文件系统上,包含输入脚本的目录在符号链接被跟随之后才被计算。换句话说,包含符号链接的目录 不会 被添加到模块搜索路径。

初始化后,Python 程序会修改 sys.path。包含当前运行脚本的目录会被放在搜索路径的首位,位于标准库之前。这意味着该目录中与标准库同名的文件将会被导入,而相应的标准库则不会被导入。这将导致错误,除非你有意替换。参考 Standard Modules 获取更多信息。

“编译的” Python 文件

为了加快模块载入速度,Python 将每个模块的编译版本以 module.*version*.pyc 的名称缓存在 __pycache__ 目录下,”version” 编码编译文件的格式,它通常包含 Python 版本号。例如,在 CPython 3.3 编译的 spam.py 文件缓存在 __pycache__/spam.cpython-33.py 中。这种命名方式允许不同发行版本和不同版本的 Python 编译文件共存。

Python 检查源文件修改日期并与编译的文件进行比较以确认编译文件是否过时,需要重新编译。这是一个全自动过程。同样的,编译的模块不依赖于操作系统,所以相同的库可以在不同架构的系统之间分享。

Python 在两种情况下不检查缓存。首先,Python 总会重新编译且不会缓存从命令行直接导入模块。其次,如果没有源模块,Python 也不会检查缓存。为了支持无源文件(只有编译文件)发布,编译的模块必须位于源目录,且不能有一个源模块。

对于高阶玩家的提醒:

  • 你可以使用 -O-OO Python 命令开关来减少一个编译模块的大小. -O 选项会去除断言声明,-OO 选项会去除断言声明和 __doc__ 字符串。由于一些程序依赖于这些变量,请只有在你明确知道自己在干嘛时使用这两个选项。“优化” 过的模块有一个 opt- 标签通常会更小。以后的版本也许会改版优化的效果。
  • .pyc 文件读取的程序不会比从 .py 文件读取的程序快;.pyc 只在载入时更快。
  • 模块 compileall 可以在一个目录下创建所有模块的 .pyc 文件。
  • 编译过程还有更多细节,包括一个决策流程图,它可以在 PEP 3147 找到。

标准模块

Python 带有一个标准模块库,并发布有独立的文档,名为 Python 库参考手册(此后称其为”库参考手册”)。有一些模块内置于解释器之中,这些操作的访问接口不是语言内核的一部分,但是已经内置于解释器了。这既是为了提高效率,也是为了给系统调用等操作系统原生访问提供接口。这类模块集合是一个依赖于底层平台的配置选项。例如,winreg 模块只提供在 Windows 系统上才有。有一个具体的模块值得注意: sys ,这个模块内置于所有的 Python 解释器。变量 sys.ps1sys.ps2定义了主提示符和辅助提示符字符串:

1
2
3
4
5
6
7
8
9
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

这两个变量只在解释器的交互模式下有意义。

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

1
2
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

dir() 函数

内置函数 dir() 用于按模块名搜索模块定义,它返回一个字符串类型的存储列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__',
'__package__', '__stderr__', '__stdin__', '__stdout__',
'_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe',
'_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv',
'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder',
'call_tracing', 'callstats', 'copyright', 'displayhook',
'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix',
'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit',
'getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount',
'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path',
'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1',
'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit',
'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout',
'thread_info', 'version', 'version_info', 'warnoptions']

无参数调用时,dir() 函数返回当前定义的命名:

1
2
3
4
5
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

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

dir() 不会列出内置函数和变量名。如果你想列出这些内容,它们在标准模块 builtins 中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
'NotImplementedError', 'OSError', 'OverflowError',
'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
'__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
'zip']

包通常是使用『圆点模块名』的结构化模块命名空间。例如,名为 A.B 的模块表示了名为 A 的包中名为 B 的子模块。正如同用模块来保存不同的模块架构可以避免全局变量之间的相互冲突,使用圆点模块名保存像 NumPy 或 Python Imaging Library 之类的不同类库架构可以避免模块之间的命名冲突。

现在,你必须开始遵守用以组织你的程序的层次结构。变量通常位于函数内部,函数与全局变量通常位于模块内部。如果你希望组织起这些模块的话,应该怎么办?这便是包(Packages)应当登场的时刻。

包是指一个包含模块与一个特殊的 __init__.py 文件的文件夹,后者向 Python 表明这一文件夹是特别的,因为其包含了 Python 模块。

让我们这样设想:你想创建一个名为“world”的包,其中还包含着 “asia”、“africa”等其它子包,同时这些子包都包含了诸如“india”、 “madagascar”等模块。

假设你现在想要设计一个模块集(一个”包”)来统一处理声音文件和声音数据。存在几种不同的声音格式(通常由它们的扩展名来标识,例如:.wav.aiff.au ),于是,为了在不同类型的文件格式之间转换,你需要维护一个不断增长的包集合。可能你还想要对声音数据做很多不同的操作(例如混音,添加回声,应用平衡 功能,创建一个人造效果),所以你要加入一个无限流模块来执行这些操作。你的包可能会是这个样子(通过分级的文件体系来进行分组):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sound/                          Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...

当导入这个包时,Python 通过 sys.path 搜索路径查找包含这个包的子目录。

为了让 Python 将目录当做内容包,目录中必须包含 __init__.py 文件。这是为了避免一个含有烂俗名字的目录无意中隐藏了稍后在模块搜索路径中出现的有效模块,比如 string。最简单的情况下,只需要一个空的 __init__.py 文件即可。当然它也可以执行包的初始化代码,或者定义稍后介绍的 __all__ 变量。

用户可以每次只导入包里的特定模块,例如:

1
import sound.effects.echo

这样就导入了 sound.effects.echo 子模块。它必需通过完整的名称来引用:

1
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

导入包时有一个可以选择的方式:

1
from sound.effects import echo

这样就加载了 echo 子模块,并且使得它在没有包前缀的情况下也可以使用,所以它可以如下方式调用:

1
echo.echofilter(input, output, delay=0.7, atten=4)

还有另一种变体用于直接导入函数或变量:

1
from sound.effects.echo import echofilter

这样就又一次加载了 echo 子模块,但这样就可以直接调用它的 echofilter() 函数:

1
echofilter(input, output, delay=0.7, atten=4)

需要注意的是使用 from package import item 方式导入包时,这个子项(item)既可以是包中的一个子模块(或一个子包),也可以是包中定义的其它命名,像函数、类或变量。import 语句首先核对是否包中有这个子项,如果没有,它假定这是一个模块,并尝试加载它。如果没有找到它,会引发一个 ImportError 异常。

相反,使用类似 import item.subitem.subsubitem 这样的语法时,这些子项必须是包,最后的子项可以是包或模块,但不能是前面子项中定义的类、函数或变量。

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

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

  • Python标准库模块
  • Python第三方模块
  • 应用程序自定义模块

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

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

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

使用 * 导入包

那么当用户写下 from sound.effects import * 时会发生什么事?理想中,总是希望在文件系统中找出包中所有的子模块,然后导入它们。这可能会花掉很长时间,并且出现期待之外的边界效应,导出了希望只能显式导入的包。

对于包的作者来说唯一的解决方案就是给提供一个明确的包索引。import 语句按如下条件进行转换:执行 from package import * 时,如果包中的 __init__.py 代码定义了一个名为 __all__ 的列表,就会按照列表中给出的模块名进行导入。新版本的包发布时作者可以任意更新这个列表。如果包作者不想 import *的时候导入他们的包中所有模块,那么也可能会决定不支持它( import * )。例如, sound/effects/__init__.py 这个文件可能包括如下代码:

1
__all__ = ["echo", "surround", "reverse"]

这意味着 from sound.effects import * 语句会从 sound 包中导入以上三个已命名的子模块。

如果没有定义 __all__from sound.effects import * 语句 不会sound.effects 包中导入所有的子模块。无论包中定义多少命名,只能确定的是导入了 sound.effects 包(可能会运行 __init__.py 中的初始化代码)以及包中定义的所有命名会随之导入。这样就从 __init__.py 中导入了每一个命名(以及明确导入的子模块)。同样也包括了前述的 import 语句从包中明确导入的子模块,考虑以下代码:

1
2
3
import sound.effects.echo
import sound.effects.surround
from sound.effects import *

在这个例子中,echosurround 模块导入了当前的命名空间,这是因为执行 from...import 语句时它们已经定义在 sound.effects 包中了(定义了 __all__ 时也会同样工作)。

尽管某些模块设计为使用 import * 时它只导出符合某种规范/模式的命名,仍然不建议在生产代码中使用这种写法。

记住,from Package import specific_submodule 没有错误!事实上,除非导入的模块需要使用其它包中的同名子模块,否则这是推荐的写法。

包内引用

如果包中使用了子包结构(就像示例中的 sound 包),可以按绝对位置从相邻的包中引入子模块。例如,如果 sound.filters.vocoder 包需要使用 sound.effects 包中的 echo 模块,它可以 from sound.Effects import echo

你可以用这样的形式 from module import name 来写显式的相对位置导入。那些显式相对导入用点号标明关联导入当前和上级包。以 surround 模块为例,你可以这样用:

1
2
3
from . import echo
from .. import formats
from ..filters import equalizer

需要注意的是显式或隐式相对位置导入都基于当前模块的命名。因为主模块的名字总是 "__main__",Python 应用程序的主模块应该总是用绝对导入。

多重目录中的包

包支持一个更为特殊的特性, path。 在包的 __init__.py 文件代码执行之前,该变量初始化一个目录名列表。该变量可以修改,它作用于包中的子包和模块的搜索功能。

这个功能可以用于扩展包中的模块集,不过它不常用。

参考资料