6. 模块

如果你退出 Python 解释器再重新进入,你所做的定义(函数和变量)就会丢失。因此,如果你想编写一个稍微长一点的程序,最好使用文本编辑器准备解释器的输入,然后使用该文件作为输入运行。这被称为创建**脚本**。随着程序变长,你可能想将其拆分成多个文件,以便于维护。你可能还想在多个程序中使用一个你编写的便利函数,而无需将其定义复制到每个程序中。

为了支持这一点,Python 提供了一种将定义放入文件并在脚本或解释器的交互式实例中使用它们的方法。这样的文件称为**模块**;模块中的定义可以**导入**到其他模块或**主**模块(你在顶级执行的脚本和计算器模式下可以访问的变量集合)中。

模块是一个包含 Python 定义和语句的文件。文件名是模块名附加后缀 .py。在一个模块中,模块名(作为字符串)可以通过全局变量 __name__ 的值获取。例如,使用你喜欢的文本编辑器在当前目录中创建一个名为 fibo.py 的文件,其内容如下:

# Fibonacci numbers module

def fib(n):
    """Write Fibonacci series up to n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib2(n):
    """Return Fibonacci series up to n."""
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

现在进入 Python 解释器并使用以下命令导入此模块:

>>> import fibo

这不会将 fibo 中定义的函数名称直接添加到当前的 命名空间 中(有关更多详细信息,请参阅 Python 作用域和命名空间);它只会在那里添加模块名称 fibo。你可以使用模块名称访问这些函数:

>>> 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'

如果你打算经常使用某个函数,可以将其赋值给一个局部名称:

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

6.1. 关于模块的更多信息

模块可以包含可执行语句和函数定义。这些语句旨在初始化模块。它们只在导入语句中**第一次**遇到模块名时执行。[1](如果文件作为脚本执行,它们也会运行。)

每个模块都有自己的私有命名空间,所有在模块中定义的函数都将其用作全局命名空间。因此,模块的作者可以在模块中使用全局变量,而无需担心与用户的全局变量意外冲突。另一方面,如果你知道自己在做什么,你可以使用与引用其函数相同的表示法 modname.itemname 来操作模块的全局变量。

模块可以导入其他模块。习惯上但不是必须将所有 import 语句放在模块(或脚本)的开头。如果导入的模块名位于模块的顶层(在任何函数或类之外),它们将被添加到模块的全局命名空间中。

有一个 import 语句的变体,它直接将模块中的名称导入到导入模块的命名空间中。例如:

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

这不会将导入来源的模块名称引入局部命名空间(因此在示例中,fibo 未定义)。

甚至还有一种变体可以导入模块定义的所有名称:

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

这会导入所有名称,除了那些以下划线 (_) 开头的名称。在大多数情况下,Python 程序员不使用此功能,因为它会向解释器引入一组未知的名称,可能会隐藏你已经定义的一些内容。

请注意,通常情况下,从模块或包中导入 * 是一种不推荐的做法,因为它通常会导致代码难以阅读。但是,在交互式会话中使用它可以节省输入。

如果模块名称后跟 as,则 as 后面的名称直接绑定到导入的模块。

>>> 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 访问。

它也可以在使用 from 时使用,效果类似:

>>> 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)

6.1.1. 将模块作为脚本执行

当你使用以下命令运行 Python 模块时:

python fibo.py <arguments>

模块中的代码将被执行,就像你导入它一样,但 __name__ 将设置为 "__main__"。这意味着通过在模块末尾添加这段代码:

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

你可以使该文件既可用作脚本,也可用作可导入模块,因为解析命令行的代码只在模块作为“主”文件执行时运行:

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

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

>>> import fibo
>>>

这通常用于为模块提供方便的用户界面,或用于测试目的(将模块作为脚本运行会执行测试套件)。

6.1.2. 模块搜索路径

当导入名为 spam 的模块时,解释器首先搜索具有该名称的内置模块。这些模块名称列在 sys.builtin_module_names 中。如果未找到,它将根据变量 sys.path 给出的目录列表搜索名为 spam.py 的文件。sys.path 从这些位置初始化:

  • 包含输入脚本的目录(或未指定文件时的当前目录)。

  • PYTHONPATH(目录名称列表,语法与 shell 变量 PATH 相同)。

  • 安装相关的默认值(根据约定包含一个 site-packages 目录,由 site 模块处理)。

更多详细信息请参阅 sys.path 模块搜索路径的初始化

备注

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

初始化后,Python 程序可以修改 sys.path。运行的脚本所在的目录被放置在搜索路径的开头,位于标准库路径之前。这意味着该目录中的脚本将被加载,而不是库目录中同名的模块。除非有意替换,否则这是一个错误。有关更多信息,请参阅 标准模块 一节。

6.1.3. “编译”Python 文件

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

Python 会检查源文件的修改日期与编译版本进行比较,以查看它是否过时需要重新编译。这是一个完全自动化的过程。此外,编译后的模块是平台无关的,因此同一个库可以在不同架构的系统之间共享。

Python 在两种情况下不检查缓存。首先,它总是重新编译并且不存储直接从命令行加载的模块的结果。其次,如果没有源模块,它不检查缓存。为了支持非源(仅编译)分发,编译模块必须位于源目录中,并且不能存在源模块。

一些专家提示:

  • 你可以使用 Python 命令上的 -O-OO 开关来减小编译模块的大小。-O 开关移除断言语句,-OO 开关同时移除断言语句和 __doc__ 字符串。由于某些程序可能依赖于这些可用性,你只应在你了解其作用时才使用此选项。“优化”模块带有 opt- 标签,通常更小。未来的版本可能会改变优化的效果。

  • 程序从 .pyc 文件读取时并不会比从 .py 文件读取时运行得更快;.pyc 文件唯一快的地方是加载速度。

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

  • 关于此过程的更多详细信息,包括决策流程图,请参阅 PEP 3147

6.2. 标准模块

Python 附带了一个标准模块库,在单独的文档《Python 库参考》(以下简称“库参考”)中进行了描述。有些模块内置在解释器中;它们提供对不属于语言核心但仍内置的操作的访问,无论是为了效率还是为了提供对操作系统原语(如系统调用)的访问。这些模块的集合是一个配置选项,也取决于底层平台。例如,winreg 模块只在 Windows 系统上提供。有一个特殊的模块值得关注:sys,它内置在每个 Python 解释器中。变量 sys.ps1sys.ps2 定义了用作主要和次要提示符的字符串:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

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

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

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

6.3. dir() 函数

内置函数 dir() 用于查找模块定义了哪些名称。它返回一个排序的字符串列表:

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
 '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
 '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
 '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
 'breakpointhook', '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', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
 'warnoptions']

不带参数时,dir() 列出你当前定义的名称:

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

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

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

>>> 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']

6.4.

包是一种通过使用“点分模块名”来构造 Python 模块命名空间的方式。例如,模块名 A.B 指示包 A 中的一个名为 B 的子模块。就像使用模块可以避免不同模块的作者担心彼此的全局变量名一样,使用点分模块名可以避免 NumPy 或 Pillow 等多模块包的作者担心彼此的模块名。

假设你想设计一个模块集合(一个“包”),用于统一处理声音文件和声音数据。有许多不同的声音文件格式(通常通过其扩展名识别,例如:.wav.aiff.au),因此你可能需要创建并维护一个不断增长的模块集合,用于在各种文件格式之间进行转换。你还可能希望对声音数据执行许多不同的操作(例如混音、添加回声、应用均衡器函数、创建人工立体声效果),因此你将不断编写模块来执行这些操作。以下是你的包的可能结构(以分层文件系统表示):

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 中的目录中搜索包子目录。

__init__.py 文件是必需的,以使 Python 将包含该文件的目录视为包(除非使用 命名空间包,这是一个相对高级的功能)。这可以防止具有共同名称的目录,例如 string,无意中隐藏在模块搜索路径上稍后出现的有效模块。在最简单的情况下,__init__.py 可以只是一个空文件,但它也可以执行包的初始化代码或设置 __all__ 变量(稍后会介绍)。

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

import sound.effects.echo

这会加载子模块 sound.effects.echo。它必须用其完整名称引用。

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

导入子模块的另一种方式是:

from sound.effects import echo

这也会加载子模块 echo,并使其无需包前缀即可使用,因此可以按如下方式使用:

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

另一种变体是直接导入所需的函数或变量:

from sound.effects.echo import echofilter

同样,这会加载子模块 echo,但这会使其函数 echofilter() 直接可用:

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

请注意,当使用 from package import item 时,item 可以是包的子模块(或子包),也可以是包中定义的其他名称,例如函数、类或变量。import 语句首先测试 item 是否在包中定义;如果没有,它假定它是一个模块并尝试加载它。如果找不到它,则会引发 ImportError 异常。

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

6.4.1. 从包中导入 *

那么当用户写 from sound.effects import * 时会发生什么呢?理想情况下,人们会希望它能以某种方式访问文件系统,找到包中存在的子模块,并全部导入它们。但这可能需要很长时间,而且导入子模块可能会产生不必要的副作用,这些副作用只应在子模块被显式导入时发生。

唯一的解决方案是包作者提供一个明确的包索引。当遇到 from package import * 时,import 语句使用以下约定:如果包的 __init__.py 代码定义了一个名为 __all__ 的列表,则将其视为应导入的模块名称列表。包作者有责任在发布新版本时保持此列表的最新状态。如果包作者认为从其包中导入 * 没有用处,他们也可以决定不支持它。例如,文件 sound/effects/__init__.py 可能包含以下代码:

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

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

请注意,子模块可能会被局部定义的名称遮蔽。例如,如果你向 sound/effects/__init__.py 文件添加了一个 reverse 函数,则 from sound.effects import * 将只导入两个子模块 echosurround,但**不**导入 reverse 子模块,因为它被局部定义的 reverse 函数遮蔽了:

__all__ = [
    "echo",      # refers to the 'echo.py' file
    "surround",  # refers to the 'surround.py' file
    "reverse",   # !!! refers to the 'reverse' function now !!!
]

def reverse(msg: str):  # <-- this name shadows the 'reverse.py' submodule
    return msg[::-1]    #     in the case of a 'from sound.effects import *'

如果未定义 __all__,则语句 from sound.effects import * **不会**将包 sound.effects 的所有子模块导入到当前命名空间中;它只确保包 sound.effects 已被导入(可能运行 __init__.py 中的任何初始化代码),然后导入包中定义的任何名称。这包括 __init__.py 中定义的任何名称(以及显式加载的子模块)。它还包括先前 import 语句显式加载的包的任何子模块。考虑这段代码:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

在此示例中,echosurround 模块被导入到当前命名空间中,因为它们在执行 from...import 语句时定义在 sound.effects 包中。(这在定义了 __all__ 时也有效。)

尽管某些模块设计为在使用 import * 时只导出遵循特定模式的名称,但在生产代码中仍然被认为是糟糕的做法。

请记住,使用 from package import specific_submodule 是完全没有问题的!事实上,这是推荐的写法,除非导入模块需要使用来自不同包的同名子模块。

6.4.2. 包内引用

当包被结构化为子包时(如示例中的 sound 包),你可以使用绝对导入来引用兄弟包的子模块。例如,如果模块 sound.filters.vocoder 需要使用 sound.effects 包中的 echo 模块,它可以使用 from sound.effects import echo

你也可以编写相对导入,使用 from module import name 形式的导入语句。这些导入使用前导点来指示相对导入中涉及的当前包和父包。例如,从 surround 模块中,你可以使用:

from . import echo
from .. import formats
from ..filters import equalizer

请注意,相对导入是基于当前模块的包名称。由于主模块没有包,因此旨在用作 Python 应用程序主模块的模块必须始终使用绝对导入。

6.4.3. 多目录中的包

包支持另一个特殊的属性,__path__。在执行包的 __init__.py 文件中的代码之前,它被初始化为一个字符串 序列,其中包含持有该包的 __init__.py 文件的目录名。此变量可以修改;这样做会影响将来对包中包含的模块和子包的搜索。

虽然这个功能不常需要,但它可以用来扩展包中找到的模块集。

脚注