Skip to main content

6. 模块

如果从Python解释器退出并再次输入,您所做的定义(函数和变量)将丢失。因此,如果要编写一个稍长的程序,最好使用文本编辑器为解释器准备输入,并以该文件作为输入运行它。这被称为创建 script。随着你的程序越来越长,你可能想把它分成几个文件,以方便维护。你可能还想使用一个你在几个程序中编写的方便的函数,而不必将它的定义复制到每个程序中。

为了支持这一点,Python有一种方法将定义放在一个文件中,并在解释器的脚本或交互式实例中使用它们。这样的文件称为 module;模块中的定义可以是 imported 到其他模块或 main 模块(您在顶层和计算器模式下执行的脚本中可以访问的变量的集合)。

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

# Fibonacci numbers module

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

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

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

>>> import fibo

这不会直接在当前符号表中输入在 fibo 中定义的函数的名称;它只输入模块名称 fibo。使用模块名称可以访问函数:

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

如果您打算经常使用函数,可以将其分配给本地名称:

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

6.1. 更多模块

模块可以包含可执行语句以及函数定义。这些语句用于初始化模块。它们只在 first 时间执行在import语句中遇到模块名称。 [1] (如果文件作为脚本执行,它们也会运行。)

每个模块都有自己的私有符号表,它由模块中定义的所有函数用作全局符号表。因此,模块的作者可以使用模块中的全局变量,而不用担心与用户的全局变量的意外冲突。另一方面,如果你知道你在做什么,你可以使用与其功能 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

这将导入除以下划线(_)开头的所有名称。在大多数情况下,Python程序员不使用这个工具,因为它在解释器中引入了一组未知的名称,可能隐藏了一些你已经定义的东西。

注意,一般来说,从模块或包中导入 * 的做法被淹没,因为它经常导致可读性差的代码。但是,可以使用它来保存在交互式会话中输入。

注解

出于效率原因,每个解释器会话仅导入每个模块一次。因此,如果更改模块,您必须重新启动解释器 - 或者,如果它只是一个模块要交互式测试,请使用 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]))

您可以使该文件可用作脚本以及可导入模块,因为解析命令行的代码只有在模块作为“main”文件执行时才会运行:

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

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

>>> import fibo
>>>

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

6.1.2. 模块搜索路径

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

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

  • PYTHONPATH (目录名称列表,其语法与shell变量 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 开关删除assert语句和__doc__字符串。由于一些程序可能依赖于这些可用的,你应该只使用这个选项,如果你知道你在做什么。 “优化”模块有一个 opt- 标签,通常较小。未来的版本可能会改变优化的效果。

  • 当从 .pyc 文件读取程序时,程序不会比从 .py 文件读取时运行得更快; .pyc 文件更快的唯一的事情是它们的加载速度。

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

  • 有关这一过程的更多细节,包括PEP 3147中决策的流程图。

6.2. 标准模块

Python附带了一个标准模块库,在一个单独的文档中描述,Python Library Reference(以下简称“库引用”)。一些模块内置在解释器中;这些提供对不是语言核心的一部分但仍然内置的操作的访问,以提高效率或提供对操作系统原语(例如系统调用)的访问。这样的模块集合是也取决于底层平台的配置选项。例如,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)  
['__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() 将列出您当前定义的名称:

>>> 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或Python成像库,不必担心每个其他的模块名称。

假设你想设计一个模块集合(一个“包”),以统一处理声音文件和声音数据。有许多不同的声音文件格式(通常由其扩展名识别,例如:.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 上的目录查找package子目录。

需要 __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 时,项目可以是包的子模块(或子包),或者在包中定义的一些其他名称,例如函数,类或变量。 import 语句首先测试该项是否在包中定义;如果没有,它假定它是一个模块并尝试加载它。如果它无法找到它,则会引发 ImportError 异常。

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

6.4.1. 从包中导入*

现在当用户写 from sound.effects import * 时会发生什么?理想情况下,人们希望这会以某种方式发送到文件系统,找到包中存在哪些子模块,并将它们全部导入。这可能需要很长时间,并且导入子模块可能具有不希望的副作用,该副作用应当仅当子模块被显式导入时才发生。

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

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

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

如果未定义 __all__,则语句 from sound.effects import * 会将 not 将所有子模块从包 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 包)时,可以使用绝对导入来引用siblings包的子模块。例如,如果模块 sound.filters.vocoder 需要使用 sound.effects 包中的 echo 模块,则可以使用 from sound.effects import echo

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

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

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

6.4.3. 多个目录中的包

软件包支持一个更特殊的属性,__path__。这被初始化为一个列表,该列表包含在执行该文件中的代码之前保存该包的 __init__.py 的目录的名称。此变量可以修改;这样做会影响对包中包含的模块和子包的未来搜索。

虽然不常需要此功能,但它可用于扩展包中包含的模块集。

脚注

[1]

事实上,函数定义也是“执行”的“语句”。模块级函数定义的执行在模块的全局符号表中输入函数名称。