6. Modules

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

为此,Python 提供了一种将定义放入文件中并在脚本或解释器的交互式实例中使用它们的方法。这样的文件称为* module ;可以将模块中的定义导入其他模块或 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

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

>>> import fibo

这不会直接在当前符号表中 Importing 在fibo中定义的Function的名称。仅在其中 Importing 模块名称fibo。使用模块名称,您可以访问以下Function:

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

如果您打算经常使用某个Function,则可以将其分配给本地名称:

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

6.1. 有关模块的更多信息

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

每个模块都有自己的专用符号表,模块中定义的所有Function都将其用作全局符号表。因此,模块的作者可以在模块中使用全局变量,而不必担心与用户的全局变量的意外冲突。另一方面,如果您知道自己在做什么,则可以使用与引用其Function相同的表示法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 程序员不使用此Function,因为它在解释器中引入了一组未知的名称,可能会隐藏您已经定义的某些内容。

请注意,通常不赞成从模块或包中导入*的做法,因为这通常会导致可读性差的代码。但是,可以使用它来保存交互式会话中的键入。

如果模块名称后跟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

Note

出于效率原因,每个模块在每个解释器会话中仅导入一次。因此,如果您更改模块,则必须重新启动解释器;或者,如果这只是您要进行交互测试的一个模块,请使用importlib.reload(),例如import importlib; importlib.reload(modulename)

6.1.1. 将模块作为脚本执行

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

python fibo.py <arguments>

该模块中的代码将被执行,就像您导入它一样,但是__name__设置为"__main__"。这意味着pass在模块末尾添加以下代码:

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.path给出的目录列表中搜索名为spam.py的文件。 sys.path从以下位置初始化:

  • 包含 Importing 脚本的目录(如果未指定文件,则为当前目录)。

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

  • 取决于安装的默认值。

Note

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

初始化后,Python 程序可以修改sys.path。包含正在运行的脚本的目录位于搜索路径的开始,在标准库路径之前。这意味着将加载该目录中的脚本,而不是库目录中相同名称的模块。除非打算进行更换,否则这是一个错误。有关更多信息,请参见Standard Modules部分。

6.1.3. “已编译” Python 文件

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

Python 根据编译版本检查源代码的修改日期,以查看其是否过时并需要重新编译。这是一个完全自动化的过程。而且,编译后的模块与平台无关,因此可以在具有不同体系结构的系统之间共享同一库。

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

maven 提示:

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

  • .pyc文件中读取程序比从.py文件中读取程序运行得更快。关于.pyc文件,唯一更快的是它们的加载速度。

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

  • PEP 3147中对此流程有更多详细信息,包括决策流程图。

6.2. 标准模块

Python 带有标准模块库,在单独的文档 Python 库参考(以下称为“库参考”)中进行了介绍。一些模块内置在解释器中。它们提供对操作的访问,这些操作不是语言核心的一部分,而是内置于操作中,以提高效率或提供对 osPrimitives(例如系统调用)的访问。此类模块的集合是一个配置选项,它也取决于基础平台。例如,winreg模块仅在 Windows 系统上提供。每个 Python 解释器都内置了一个特定的模块:sys。变量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. Packages

包是pass使用“点分模块名称”来构造 Python 模块名称空间的一种方式。例如,模块名称A.B表示包A中的子模块B。就像使用模块使不同模块的作者不必担心彼此的全局变量名称一样,使用点分模块名称可以使诸如 NumPy 或 Pillow 之类的多模块程序包的作者不必担心彼此的模块名称。 。

假设您要设计模块的集合(“包”)以统一处理声音文件和声音数据。声音文件格式有很多(通常由其 extensions 识别,例如.wav.aiff.au),因此您可能需要创建并维护越来越多的模块集合,以在各种文件格式之间进行转换。您可能还需要对声音数据执行许多不同的操作(例如混合,添加回声,应用均衡器Function,创建人工立体声效果),因此此外,您将编写永无休止的模块流来执行这些操作。这是您的包的可能结构(以分层文件系统表示):

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,但这将使其Functionechofilter()直接可用:

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

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

相反,当使用类似import item.subitem.subsubitem的语法时,除最后一项外,其他各项都必须是一个包装;最后一项可以是模块或包,但不能是上一项中定义的类或函数或变量。

6.4.1. Importing * From a Package

现在,当用户写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 *不会*将所有子模块从包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. Intra-package References

当包被结构化为子包时(如示例中的sound包一样),可以使用绝对导入来引用同级包的子模块。例如,如果模块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的目录名称的列表。这个变量可以修改;这样做会影响以后对包中包含的模块和子包的搜索。

尽管此Function不是经常需要的,但可用于扩展软件包中找到的模块集。

Footnotes

  • [1]
    • 实际上,函数定义也是“执行”的“语句”。执行模块级Function定义会将Function名称 Importing 模块的全局符号表中。