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. 深入模块¶
模块可以包含可执行语句以及函数定义。这些语句用于初始化模块。只有在导入语句中第一次遇到模块名称时,才会执行。[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程序员不要使用这个便利的方法,因为它会引入一系列未知的名称到解释器中,这很可能覆盖你已经定义的一些东西。
注意通常情况下从其他module或package中导入*
是不被赞同的,因为这会降低代码的可读性。但是,可以使用它来保存在交互式会话中输入。
注意
出于效率的原因,每个模块仅每个解释器会话导入一次。因此, 如果你更改了你的模块,你必须重启解释器——或者,如果你想用交互的方式测试你的模块,可以使用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
,其中version表示编译好的文件的格式;它一般包含Python的版本号。例如,在CPython 3.3中,spam.py编译好的版本将缓存为 __pycache__/spam.cpython-33.pyc
。这种命名约定允许来自不同版本和不同版本的Python的编译模块共存。
Python会检查源文件和编译好的版本的修改日期,来查看它是否过期以及需要重新编译。这是一个完全自动的过程。此外,编译的模块是平台无关的,因此相同的库可以在具有不同架构的系统之间共享。
Python在两种情况下不检查缓存。第一,它会始终重新编译而且不保存直接从命令行加载的模块。第二,如果没有源模块,它不检查缓存。要支持非源(仅编译)分发,编译的模块必须位于源目录中,并且不能有源模块。
一些专家的提示:
- 你可以在Python命令中使用
-O
或者-OO
开关来减少编译好的模块的大小。-O
开关移除assert语句,-OO
移除assert语句和__doc__字符串。因为某些程序可能会依赖于具有它们,你应该只在你知道做什么的情况下使用此选项。“优化”后的模块有一个opt-
标记,而且通常会小一些。未来的版本可能会修改优化的效果。 - 从
.pyc
文件读取的程序不会比从.py
文件读取的程序运行得更快,.pyc
文件唯一快的地方在于它们加载的速度。 compileall
模块可以为一个目录下的所有模块创建.pyc文件。- 这个过程还有更多的细节,包括决策的流程图,参见PEP 3147。
6.2. 标准模块¶
Python附带一个标准模块库,在一个单独的文档中描述,Python库参考(以下称为“库参考”)。有些模块被直接构建在解析器里;这些操作虽然不是语言核心的部分,但是依然被内建进来,一方面是效率的原因,另一方面是为了提供访问操作系统原语,如系统调用的功能。这样的模块集合是也取决于底层平台的配置选项。例如,winreg
模块只在Windows系统上提供。有一个特别的模块值得注意:sys
,它内置在每一个Python解析器中。变量sys.ps1
和sys.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 Imaging库,不必担心每个其他的模块名称。
假设你想设计一个容器模块(一个“包”),用于统一处理声音文件和声音数据。有很多不同的声音文件格式 (通常用其扩展名识别,例如︰ .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
这样的语法时,除了最后一项其它每项必须是一个包;最后一项可以是一个模块或一个包,但不能是在前一个项目中定义的类、函数或变量。
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 *
语句 不 会从 sound.effects
包中导入所有的子模块到当前命名空间;它只保证sound.effects
包已经被导入(可能会运行__init__.py
中的任何初始化代码),然后导入包中定义的任何名称。这包括由 __init__.py
定义的任何名称(以及它显式加载的子模块)。还包括这个包中已经由前面的import
语句显式加载的子模块。考虑这个代码:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
在这个例子中,当from...import
语句执行时,echo
和surround
模块被导入到当前命名空间,因为它们定义在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
请注意,相对导入基于当前模块的名称。因为主模块的名字总是 "__main__"
,Python 应用程序的主模块应该总是用绝对导入。