6. 模块

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

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

模块是包含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 直接在当前符号表中输入模块名称 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 导入后,解释器首先搜索具有该名称的内置模块。如果找不到,它将搜索名为 spam.py 在变量给出的目录列表中 sys.path . sys.path 从以下位置初始化:

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

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

  • 依赖于安装的默认值。

注解

在支持symlinks的文件系统上,在执行symlink之后计算包含输入脚本的目录。换句话说,包含符号链接的目录是 not 添加到模块搜索路径。

初始化后,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不会检查缓存。首先,它总是重新编译,不存储直接从命令行加载的模块的结果。其次,如果没有源模块,则不会检查缓存。要支持非源(仅编译)分发,已编译模块必须位于源目录中,并且不能有源模块。

专家的一些建议:

  • 你可以使用 -O-OO 打开python命令以减小已编译模块的大小。这个 -O switch删除assert语句, -OO switch同时删除assert语句和 __doc__ 串。由于某些程序可能依赖于这些可用的程序,因此只有在知道自己在做什么的情况下才应使用此选项。”优化的“模块 opt- 标签和通常较小。未来的版本可能会改变优化的效果。

  • 当程序从 .pyc 文件,而不是从 .py 文件;唯一更快的东西 .pyc 文件是加载文件的速度。

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

  • 关于这个过程有更多的细节,包括决策流程图 PEP 3147 .

6.2. 标准模块

python附带了一个标准模块库,在单独的文档python library reference(“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)  
['__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 指定名为 B 在名为的包中 A . 就像使用模块可以避免不同模块的作者担心彼此的全局变量名一样,使用点式模块名可以避免多模块包的作者担心彼此的模块名,如numpy或bellow。

假设您想要设计一个模块集合(一个“包”),用于统一处理声音文件和声音数据。有许多不同的声音文件格式(通常通过其扩展名来识别,例如: .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 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 模块在当前命名空间中导入,因为它们是在 sound.effects 封装时 from...import 语句被执行。(这也适用于 __all__ 定义。

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

记住,使用没有什么问题 from package import specific_submodule !实际上,除非导入模块需要使用来自不同包的同名子模块,否则这是推荐的表示法。

6.4.2. 包内引用

当包被构造成子包时(如 sound 在示例中,可以使用绝对导入来引用兄弟包的子模块。例如,如果模块 sound.filters.vocoder 需要使用 echo 模块中 sound.effects 封装,可以使用 from sound.effects import echo .

您还可以使用 from module import name 输入声明的格式。这些导入使用前导点指示相关导入中涉及的当前和父包。从 surround 例如,模块,您可以使用:

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

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

6.4.3. 多个目录中的包

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

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

脚注

1

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