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 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. 有关模块的更多信息

一个模块可以包括可执行的声明以及函数定义。这些声明是用来初始化模块的。它们只有在当模块名第一次出现在调入名令中时会被执行。(当文件被当作脚本运行时,它们也会执行。)

每个模块都有它自己的私有符号表,该表被定义在该模块里的所有函数当作全局符号表使用。因此,一个模块的作者可以在模块内放心使用全局变量,而不必担心它们会和模块使用者的全局变量发生意外冲突。另一方面,如果您明白接下来这么做的影响,您可以用跟访问模块内的函数的同样标记方法,去访问一个模块的全局变量,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程序员都不会使用这个功能,因为它在解释器中引入了一组未知的名称,而它们很可能会覆盖一些你已经定义过的东西。

注意通常情况下从一个模块或者包内调入 * 的做法是不太被接受的, 因为这通常会导致可读性很差的代码。不过,在交互式编译器中为了节省打字可以这么用。

如果模块名后跟有 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 的名称存在的。

It can also be used when utilising from with similar effects:

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

您既可以把这个文件当作脚本又可当作一个可调入的模块来使用, 因为那段解析命令行的代码只有在当模块是以“main”文件的方式执行的时候才会运行:

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

如果模块是被导入的,那些代码是没运行的:

>>> import fibo
>>>

这经常要么用来给模块提供一个方便的用户接口,要么用于测试(以脚本的方式运行模块从而执行一些测试套件)。

6.1.2. 模块搜索路径

当一个名为 spam 的模块被导入的时候,解释器首先寻找同名的内置模块。如果没有找到,然后解释器从 sys.path 变量给出的目录列表里寻找 spam.pysys.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 开关同时去除断言声明和文档字符串。由于有些程序或许依赖于这些声明和文档,您应当只有在清楚这么做的后果时才使用这个选项。“优化过的”模块有一个 opt- 标签并且通常小些。将来的发行版本或许会更改优化的效果。
  • 一个从 .pyc 文件读出的程序并不会比它从 .py 读出时运行的更快,.pyc 文件唯一快的地方在于载入速度。
  • compileall 模块可以为一个目录下的所有模块创建.pyc文件。
  • 关于这个过程,PEP 3147 中有更多细节,包括一个决策流程图。

6.2. 标准模块

Python自带一个标准模块的库,它在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)  
['__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() 不会列出内置的函数名和变量名。 如果您想要这些,它们的定义是在标准模块:mod: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模块的方式。 例如,模块名:mod:A.B 指代“A”包中名为“B”的子模块。正如模块的使用可以免除不同模块开发者担忧各自的宏变量名,使用加点的模块名,可以免除多模块的包如Numpy或Pillowd开发者,担忧各自的模块名。

假设你想为声音文件和声音数据的统一处理,设计一个模块集合(一个“包”)。由于存在很多不同的声音文件格式(通常由它们的扩展名来识别,例如:.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 里的目录搜索包含这个包的路径。

为了让 Python 将目录当做包,目录中必须包含 __init__.py 文件。这是为了避免一个含有烂俗名字的目录无意中隐藏了稍后在模块搜索路径中出现的有效模块,比如 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 这样的语法时,除了last之外的每个项都必须是一个包;最后一项可以是模块或包,但不能是类或函数或变量。

6.4.1. 使用 * 导入

Now what happens when the user writes from sound.effects import *? Ideally, one would hope that this somehow goes out to the filesystem, finds which submodules are present in the package, and imports them all. This could take a long time and importing sub-modules might have unwanted side-effects that should only happen when the sub-module is explicitly imported.

The only solution is for the package author to provide an explicit index of the package. The import statement uses the following convention: if a package’s __init__.py code defines a list named __all__, it is taken to be the list of module names that should be imported when from package import * is encountered. It is up to the package author to keep this list up-to-date when a new version of the package is released. Package authors may also decide not to support it, if they don’t see a use for importing * from their package. For example, the file sound/effects/__init__.py could contain the following code:

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

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

如果没有定义 __all__from sound.effects import * 语句*不*会从包 sound.effects 中导入所有子模块到当前命名空间;它只确保导入了包 sound.effects (可能运行任何在 __init__.py 中的初始化代码),然后导入包中定义的任何名称。

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

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

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

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

6.4.3. 多个目录中的包

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

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

脚注

[1]实际上,函数定义也是‘声明’,并会运行;一个模块函数定义的运行会进入到此模块全局符号表里的此函数名。