退出Python解释器后,再次进入时,之前在Python解释器中定义的函数和变量就丢失了。因此,编写较长程序时,建议用文本编辑器代替解释器,执行文件中的输入内容,这就是编写脚本。随着程序越来越长,为了方便维护,最好把脚本拆分成多个文件。编写脚本还有一个好处,不同程序调用同一个函数式,不用每次把函数复制到各个程序。
为实现这些需求,Python把各种定义存入一个文件,在脚本或解释器的交互式实例中使用。这个文件就是模块;模块中定义可以导入到其他模块或主模块(在顶层和计算器模式下,执行脚本中可访问的变量集)。
模块是包含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(1000)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
>>> fibo.__name__
'fibo'
如果经常使用某个函数,可以把它赋值给局部变量:
>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
1.模块详解
模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在import语句第一次遇到模块名时执行。
模块有自己的私有符号表,用作模块中所有函数的全局符号表。因此,在模块内使用全局变量时,不用担心与用户定义的全局变量发生冲突。另一方面,可以用与访问模块函数一样的标记法,访问模块的全局变量,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 *
>>> fib2(500)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
这种方式会导入所有不以下划线(_)开头的名称。大多数情况下,不要用这个功能,这种方式向解释器导入了一批未知的名称,可能会覆盖已经定义的名称。
注意,一般情况下,不建议从模块或包内导入*,因为,这项操作经常让代码变得难以理解。不过,为了在交互式编译器中少打几个字,这么用也没问题。
模块名后使用as时,直接把as后面的名称与导入模块绑定。
>>> import fibo as fib
>>> fib.fib(10)
0 1 1 2 3 5 8
与import fibo一样,这种方式也可以有效地导入模块,唯一的区别是,导入的名称是fib。
from中也可以使用这种方式,效果类似:
>>> from fibo import fib2 as fibonacci
>>> fibonacci(10)
[0, 1, 1, 2, 3, 5, 8]
注解:为了保证运行效率,每次解释器会话只导入一次模块。如果更改了模块内容,必须重启解释器;仅交互测试一个模块时,也可以使用importlib.reload(),例如import importlib;
importlib.reload(modulename)。
1.1 以脚本方式执行模块
可以用以下方式运行Python模块:
python fibo.py <arguments>
这项操作将执行模块里的代码,和导入模块一样,但会把__name__赋值为“__main__”。也就是把下列 代码添加到模块末尾:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1])
既可以把这个文件当脚本使用,也可以用作导入的模块,因为,解析命令行的代码只有在模块以“main”文件执行时才会运行:
C:\Users\Windows10\Desktop>python fibo.py 100
0 1 1 2 3 5 8 13 21 34 55 89
导入模块时,不运行这些代码:
>>> import fibo
>>>
这种操作常用于为模块提供便捷用户接口,或用于测试(把模块当作执行测试套件的运行脚本)。
1.2 模块搜索路径
When a module named spam is imported. the interpreter first searches for a built-in module with that name. These module names are listed in sys.builtin_modules_names. If not found, it thean searches for a file named spam.py in a list of directories given by the variable sys.path,sys.path is initialized from these locations:
- 输入脚本的目录(或未指定文件时的当前目录)
- PYTHONPATH(目录列表,与shell变量PATH的语法一样)
- The installation-dependent default (by convention including a site-packages directory, handled by th site module).
注解:在支持symlink的文件系统中,输入脚本目录是在追加symlink后计算出来的。换句话说,包含symlink的目录并没有添加至模块搜索路径。
初始化后,Python程序可以更改sys.path。运行脚本的目录在标准库路径之前,置于搜索路径的开头。即,加载的是该目录里的脚本,而不是标准库的同名模块。除非刻意替换,否则会报错。
1.3 “已编译的”Python文件
为了加快加载模块,Python把模块的编译版缓存在__pycache__目录中,文件名为module.version.pyc,version对编译文本格式进行编码,一般是Python的版本号。例如,CPython的3.3发行版中,spam.py的编译版本缓存为__pycache__/spam.cpython-33.pyc。使用这种命名惯例,可以让不同Python发行版及不同版本的已编译模块共存。
Python对比编译版本与源码的修改日期,查看它是否过期,是否要重新编译,此过程完全自动化。此外,编译模块与平台无关,因此,可在不同架构系统之间共享相同的支持库。
Python在两种情况下不检查缓存。其一,从命令行直接载入模块,只重新编译,不存储编译结果;其二,没有源模块,就不会检查缓存。为了支持无源文件(仅编译)发行版本,编译模块必须在源目录下,并且绝不能有源模块。
2.标准模块
Python自带一个标准模块的库,它在Python库参考(此处以下称为“库参考”)里另外描述。一些模块是内嵌到编译器里面的,它们给一些虽并非语言核心但却内嵌的操作提供接口,要么是为了效率,要么是给操作系统基础操作例如系统调入提供接口。这些模块集是一个配置选项,并且还依赖于底层的操作系统。例如,winreg模块只是Windows系统上提供。一个特别值得注意的模块sys,它被内嵌到每一个Python编译器中。sys.ps1和sys.ps2变量定义了一些字符,它们可以用作主提示符合辅助提示符:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = '-->'
-->print("Yuch!")
Yuch!
-->
只有解释器用于交互模式时,才定义这两个变量。
变量sys.path是字符串列表,用于确定解释器的模块搜索路径。该变量以环境变量PYTHONPATH提取的默认路径进行初始化,如未设置PYTHONPATH,则使用内置的默认路径。可以用标准列表操作修改该变量。
3.dir()函数
内置函数dir()用于查找模块定义的名称。返回结果是经过排序的字符串列表:
>>> import fibo, sys
>>> dir(fibo)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fib', 'fib2']
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__', '_base_executable', '_clear_type_cache', '_current_exceptions', '_current_frames', '_deactivate_opcache', '_debugmallocstats', '_enablelegacywindowsfsencoding', '_framework', '_getframe', '_git', '_home', '_xoptions', 'addaudithook', 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing', 'copyright', 'displayhook', 'dllhandle', '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', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'getwindowsversion', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'orig_argv', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'platlibdir', 'prefix', 'ps1', 'ps2', 'pycache_prefix', 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdlib_module_names', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info', 'warnoptions', 'winver']
>>>
没有参数时,dir()列出当前定义的名称:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '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', 'EncodingWarning', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'aiter', 'all', 'anext', 'any', 'ascii', 'bin', 'bool', 'breakpoint', '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']
4.包
包是一种用“点式模块名”构造Python模块命名空间的方法。例如,模块A.B表示包A中名为B的子模块。正如模块可以区分不同模块之间的全局变量名称一样,点式模块名可以区分NumPy或Pillow等不同多模块包之间的模块名称。
假设要为统一处理声音文件与声音数据设计一个模块集(“包”)。声音文件的格式很多(通常以扩展名来识别,例如:.wav, .aiff, .au),因此,为了不同文件格式之间的转换,需要创建和维护一个不断增长的模块集合。为了实现对声音数据的不同处理(例如,混声,添加回声,均衡器功能,创造人工立体声效果),还要编写无穷无尽的模块流。下面这个分级文件树展示了这个包的架构:
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
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)
import语句的另一种变体是直接导入所需的函数或变量:
from sound.effects.echo import echofilter
同样,这样也会加载子模块echo,但可以直接使用函数echofilter():
echofilter(input, output, delay=0.7, atten=4)
注意,使用from package import item时,item可以是包的子模块(或子包),也可以是包定义的函数、类或便令等其他名称。import语句首先测试包中是否定义了item;如果未在包中定义,则假定item是模块,并尝试加载。如果找不到item,则触发ImportError异常。
相反,使用import item.subitem.subsubitem语句时,除最后一项外,每个item都必须是包;最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量。
4.1 从包中导入*
使用from sound.effects import * 时会发生什么?理想情况下,该语句在文件系统查找并导入包的所有子模块。这项操作花费的时间较长,并且导入子模块可能会产生不必要的副作用,这种副作用只有在显式导入子模块时才会发生。
唯一的解决方案是提供包的显式索引。import语句使用如下惯例:如果包的__init__.py代码定义了列表__all__,运行from package import * 时,它就是用于导入的模块名列表。发布包的新版本时,包的作者应更新此列表。如果包的作者认为没有必要在包中执行导入* 操作,也可以不提供此列表。例如,sound/effects/__init__.py文件包含以下代码:
__all__ = ["echo", "surround", "reverse"]
This would mean that 'from sound.effects import *' would import the three named submodules of the 'sound.effects' package.
如果没有定义__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没有任何问题!实际上,除了导入模块使用不同包的同名子模块之外,这种方式时推荐的。
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程序的主模块必须始终使用绝对导入。
4.3 多目录中的包
包还支持特殊属性__path__。该属性初始化为在包的__init__.py文件中的代码执行前所在的目录名列表。这个变量可以修改,但这样会影响将来搜索包中模块和子包的操作。
这个功能虽然不常用,但可用于扩展包中的模块集。