一文读懂!Python 模块与包,代码复用和管理的秘密武器
在 Python 编程中,模块和包是组织代码、提高代码复用性和可维护性的重要工具。本文将依据 Python 官方文档,详细介绍模块与包的相关知识,助力你快速掌握这一关键编程概念。
一、模块基础
(一)模块是什么
模块实际上就是包含 Python 定义和语句的文件,其文件名遵循 “模块名.py” 的格式。模块能够将程序拆分成多个便于管理的文件,在不同程序中,我们无需重复编写相同函数,直接调用模块中的函数即可。例如,我们可以创建一个名为fibo.py
的模块,用于实现斐波那契数列相关功能。
(二)模块的导入
- 基本导入方式:使用
import
语句导入模块,如import fibo
。这种方式不会将模块内定义的函数直接添加到当前命名空间,而是把模块名添加进去,之后通过模块名.函数名
的形式来调用函数,像fibo.fib(1000)
。 - 导入特定函数:
from fibo import fib,fib2
语句可以将指定的函数直接导入到当前命名空间,如此便可以直接使用函数名调用,无需加上模块名前缀,即fib(500)
。 - 导入所有非下划线开头的名称:
from fibo import *
能够导入模块中所有不以下划线开头的名称。不过,这种方式可能会覆盖已定义的名称,导致代码可读性变差,所以在生产代码中一般不推荐使用,但在交互式会话中为减少输入量可以使用。 - 重命名导入:使用
as
关键字可以对导入的模块或函数进行重命名。比如import fibo as fib
,这样导入后使用fib
代替fibo
来访问模块内的函数;from fibo import fib as fibonacci
则是将fib
函数重命名为fibonacci
。
二、模块进阶
(一)以脚本方式执行模块
当我们以python fibo.py <arguments>
这种方式运行模块时,模块中的__name__
会被赋值为"__main__"
。基于这个特性,我们可以在模块末尾添加如下代码:
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
这样一来,该模块既可以当作脚本直接运行,接收命令行参数执行相应功能;也能够被其他模块导入使用,且在被导入时,上述解析命令行参数的代码不会运行。这一特性常用于为模块提供便捷的用户接口或进行测试。
(二)模块搜索路径
Python 解释器在导入模块时,会按照特定顺序进行搜索。首先搜索内置模块,这些模块的名称存储在sys.builtin_module_names
中。若未找到,则会在sys.path
所包含的目录列表中查找对应的.py
文件。sys.path
的初始化包含以下几个部分:
- 被命令行直接运行的脚本所在的目录(若未指定文件,则为当前目录)。需要注意的是,在支持符号链接的文件系统中,是符号链接最终指向的目录被添加到搜索路径,而非符号链接所在目录。
PYTHONPATH
环境变量指定的目录列表(其语法与PATH
环境变量类似)。- 依赖于安装的默认值,通常包含
site-packages
目录,由site
模块进行处理。
此外,Python 程序在运行过程中可以修改sys.path
。由于脚本所在目录优先于标准库路径被搜索,如果脚本目录中有与标准库同名的文件,默认会加载脚本目录中的文件,这可能会引发错误,除非是有意为之。
(三)“已编译的” Python 文件
为了提高模块的加载速度,Python 会将模块的编译版本缓存到__pycache__
目录中,文件命名格式为module.version.pyc
,其中version
用于编码编译文件格式,一般是 Python 的版本号,例如__pycache__/spam.cpython-33.pyc
。Python 会自动对比编译版与源码的修改日期,判断编译版是否过期,进而决定是否重新编译,而且编译模块与平台无关,可在不同架构的系统间共享。
不过,在两种情况下 Python 不会检查缓存:一是从命令行直接载入的模块,每次都会重新编译且不储存编译结果;二是没有源模块时,不会检查缓存。若要分发隐藏源代码的库,可以将编译后的模块放在源目录而非缓存目录,并且源目录不能包含同名的未编译源模块。另外,在 Python 命令中使用-O
或-OO
开关,可以减小编译模块的大小,-O
会去除断言语句,-OO
不仅去除断言语句还会去除__doc__
字符串,但使用这两个选项时需谨慎,因为有些程序可能依赖这些内容。“优化过的” 模块会带有opt -
标签,文件通常会更小。compileall
模块可以为一个目录下的所有模块创建.pyc
文件。
三、标准模块
Python 自带了一个标准模块库,在 Python 库参考中有详细描述。部分模块内嵌到解释器中,为一些操作提供接口,这些接口有的是为了提高效率,有的是用于与操作系统底层操作交互,例如winreg
模块仅在 Windows 系统上可用。
sys
模块是一个特别重要的内嵌模块,其中sys.ps1
和sys.ps2
变量分别定义了主提示符和辅助提示符,不过这两个变量仅在解释器处于交互模式时才会定义。sys.path
是一个字符串列表,用于确定解释器的模块搜索路径,它会以环境变量PYTHONPATH
提取的默认路径进行初始化,如果未设置PYTHONPATH
,则使用内置的默认路径,并且可以通过标准列表操作对其进行修改,如sys.path.append('/ufs/guido/lib/python')
。
四、dir()
函数
dir()
是一个内置函数,主要用于查找模块定义的名称,返回的结果是一个经过排序的字符串列表。当不带参数调用dir()
时,它会列出当前已定义的所有名称,包括变量、模块、函数等。需要注意的是,dir()
不会列出内置函数和变量的名称,这些内容定义在标准模块builtins
中,可以通过dir(builtins)
来查看。例如:
import fibo, sys
print(dir(fibo))
print(dir(sys))
a = [1, 2, 3, 4, 5]
import fibo
fib = fibo.fib
print(dir())
import builtins
print(dir(builtins))
五、包
(一)包的定义与架构
包是一种通过 “带点号模块名” 来构建 Python 模块命名空间的方式,使用包可以有效避免模块名冲突,就像模块能避免全局变量名冲突一样。例如,A.B
表示A
包中的B
子模块。
以设计一个处理声音文件和数据的包为例,其架构可能如下:
sound/ # 最高层级的包
__init__.py # 初始化sound包
formats/ # 用于文件格式转换的子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
…
effects/ # 用于音效的子包
__init__.py
echo.py
surround.py
reverse.py
…
filters/ # 用于过滤器的子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
…
在这个架构中,每个包或子包目录下都必须有__init__.py
文件(在使用 namespace package 这种相对高级的特性时除外),该文件可以为空,也可以包含包的初始化代码或设置__all__
变量。
(二)包的导入
- 导入子模块的多种方式
- 可以导入整个子模块,如
import 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
,这样echofilter()
函数就可以直接使用,即echofilter(input, output, delay = 0.7, atten = 4)
。 - 从包中导入
*
的情况:当使用from sound.effects import *
时,并不会导入包中的所有子模块。如果包的__init__.py
文件中定义了__all__
列表,那么运行该语句时会导入列表中的模块名;若未定义__all__
,则只会确保包被导入(可能会运行__init__.py
中的初始化代码),然后导入包中已定义的名称,包括__init__.py
中定义的名称以及之前已显式加载的子模块。例如,若sound/effects/__init__.py
中包含__all__ = ["echo", "surround", "reverse"]
,那么from sound.effects import *
会导入这三个子模块;若在__init__.py
中定义了与子模块同名的函数,如reverse
函数,可能会导致子模块被遮挡而无法导入。 - 相对导入:在由多个子包构成的包中,模块既可以使用绝对导入引用同级包的子模块,如
from sound.effects import echo
;也可以使用相对导入,通过前导点号表示相对位置,如from. import echo
表示从当前包导入echo
模块,from.. import formats
表示从上级包导入formats
子包,from..filters import equalizer
表示从上级包的filters
子包导入equalizer
模块。但需要注意的是,主模块的导入语句必须始终使用绝对导入,因为主模块名永远是"__main__"
。 - 多目录中的包:包有一个特殊属性
__path__
,在执行包内代码前,它会被初始化为包含__init__.py
文件目录名称的字符串序列。这个变量可以被修改,修改后会影响后续对模块和包中包含的子包的搜索,虽然该功能不常用,但可用于扩展包中的模块集。
掌握模块与包的使用,能让你在 Python 编程中更好地组织和管理代码,提高开发效率和代码质量。希望本文能帮助你对 Python 模块与包有更深入的理解和运用。
作者:tekin