模块#
视频
随着程序越来越多,仅仅靠单个文件管理代码是很不好的行为。可以把程序分成不同逻辑单元并放入不同的文件(一般后缀为 .py
)中,这些文件在 Python 中称为 模块。
模块中的定义可以导入到其他模块或主模块(在顶层和计算器模式下,执行脚本中可访问的变量集)。
在模块内部,通过全局变量 __name__
可以获取模块名(即字符串)。
例如,用文本编辑器在目录 examples/modules/
下创建 fibo.py
文件,输入以下内容:
def fib(n):
'''打印斐波那契数列,直至 n'''
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
def fib2(n):
'''返回斐波那契数列,直至 n'''
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a+b
return result
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
用以下命令导入该模块:
%cd examples/modules
/home/runner/work/pybook/pybook/doc/pythonx/basic/examples/modules
/opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/IPython/core/magics/osm.py:417: UserWarning: This is now an optional IPython functionality, setting dhist requires you to install the `pickleshare` library.
self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
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
模块详解#
模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在 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
这段代码不会把模块名导入到局部符号表里。
还有一种变体可以导入模块内定义的所有名称:
from fibo import *
fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
这种方式会导入所有不以下划线(_
)开头的名称。大多数情况下,不要用这个功能,这种方式向解释器导入了一批未知的名称,可能会覆盖已经定义的名称。
模块名后使用 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)
。
基础#
以脚本方式执行模块#
可以用以下方式运行 Python 模块:
python fibo.py <arguments>
这项操作将执行模块里的代码,和导入模块一样,但会把 __name__
赋值为 "__main__"
。 也就是把下列代码添加到模块末尾:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
既可以把这个文件当脚本使用,也可以用作导入的模块,因为,解析命令行的代码只有在模块以 “main
” 文件执行时才会运行:
!python fibo.py 50
0 1 1 2 3 5 8 13 21 34
导入模块时,不运行这些代码:
import fibo
这种操作常用于为模块提供便捷用户接口,或用于测试(把模块当作执行测试套件的脚本运行)。
模块搜索路径#
导入 spam
模块时,解释器首先查找名为 spam
的内置模块。如果没找到,解释器再从 sys.path
变量中的目录列表里查找 spam.py
文件。sys.path
初始化时包含以下位置:
输入脚本的目录(或未指定文件时的当前目录)。
PYTHONPATH
(目录列表,与 shell 变量PATH
的语法一样)。依赖于安装的默认值(按惯例包括一个
site-packages
目录,由site
模块处理)。
备注
在支持 symlink 的文件系统中,输入脚本目录是在追加 symlink 后计算出来的。换句话说,包含 symlink 的目录并 没有 添加至模块搜索路径。
初始化后,Python 程序可以更改 sys.path
。运行脚本的目录在标准库路径之前,置于搜索路径的开头。即,加载的是该目录里的脚本,而不是标准库的同名模块。 除非刻意替换,否则会报错。
“已编译的” 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
去除断言语句,-OO
去除断言语句和__doc__
字符串。有些程序可能依赖于这些内容,因此,没有十足的把握,不要使用这两个选项。“优化过的” 模块带有opt-
标签,并且文件通常会一小些。将来的发行版或许会改进优化的效果。从 .pyc 文件读取的程序不比从 .py 读取的执行速度快,.pyc 文件只是加载速度更快。
compileall
模块可以为一个目录下的所有模块创建.pyc
文件。
本过程的细节及决策流程图,详见 PEP 3147。
标准模块#
Python 自带一个标准模块的库,它在 Python 库参考(此处以下称为"库参考" )里另外描述。 一些模块是内嵌到编译器里面的, 它们给一些虽并非语言核心但却内嵌的操作提供接口,要么是为了效率,要么是给操作系统基础操作例如系统调入提供接口。 这些模块集是一个配置选项, 并且还依赖于底层的操作系统。 例如,winreg
模块只在 Windows 系统上提供。一个特别值得注意的模块 sys
,它被内嵌到每一个 Python 编译器中。sys.ps1
和 sys.ps2
变量定义了一些字符,它们可以用作主提示符和辅助提示符:
import sys
sys.ps1
'In : '
sys.ps2
'...: '
sys.ps1 = 'C> '
只有解释器用于交互模式时,才定义这两个变量。
变量 sys.path
是字符串列表,用于确定解释器的模块搜索路径。该变量以环境变量 PYTHONPATH
提取的默认路径进行初始化,如未设置 PYTHONPATH
,则使用内置的默认路径。可以用标准列表操作修改该变量:
import sys
sys.path.append('/ufs/guido/lib/python')
dir
函数#
内置函数 dir()
用于查找模块定义的名称。返回结果是经过排序的字符串列表:
import fibo, sys
dir(fibo)
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'fib',
'fib2']
没有参数时,dir()
列出当前定义的名称:
dir()[:10]
['In', 'Out', '_', '_13', '_14', '_16', '_4', '_5', '__', '___']
提示
该函数列出所有类型的名称:变量、模块、函数等。
dir()
不会列出内置函数和变量的名称。这些内容的定义在标准模块 builtins
里:
import builtins
dir(builtins)[:10]
['ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
'BaseExceptionGroup',
'BlockingIOError',
'BrokenPipeError',
'BufferError',
'BytesWarning',
'ChildProcessError']
包#
包是一种用“点式模块名”构造 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
__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)
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
都必须是包;最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量。