Appearance
18|模块与包
Python 代码写在 .py 文件里,一个文件就是一个模块。模块把相关代码组织在一起,可以被其他文件导入复用。多个模块按目录组织,形成包。
一、模块
math.py:
python
"""数学工具模块。"""
PI = 3.14159
def circle_area(radius):
"""计算圆的面积。"""
return PI * radius ** 2
def circle_perimeter(radius):
"""计算圆的周长。"""
return 2 * PI * radius另一个文件导入使用:
python
import math
print(math.PI)
print(math.circle_area(5))注意:这里导入的是当前目录下的 math.py,不是 Python 标准库的 math 模块。如果当前目录和标准库有同名模块,当前目录的优先——这是一个常见的命名冲突陷阱。模块名不要和标准库冲突(如 math.py、sys.py、json.py)。
二、导入方式
python
import math # 导入整个模块,用 math.xxx 访问
import math as m # 起别名,用 m.xxx 访问
from math import circle_area # 只导入指定函数,直接用 circle_area
from math import * # 导入所有公开名称(不推荐)from math import circle_area 可以直接用函数名,但失去了模块前缀,如果多个模块有同名函数会冲突。
from math import * 导入模块中 __all__ 列表定义的所有名称。如果没有 __all__,则导入所有不以 _ 开头的名称。这种写法让代码的依赖关系不清晰——看到 circle_area() 不知道它来自哪个模块。除非在交互式环境里快速测试,否则不推荐。
三、模块搜索路径
导入模块时,Python 按以下顺序查找:
- 当前目录
PYTHONPATH环境变量指定的目录- 标准库目录
- 第三方包安装目录(site-packages)
查看搜索路径:
python
import sys
print(sys.path)四、if __name__ == "__main__"
模块既可以被导入,也可以直接执行。if __name__ == "__main__" 区分这两种场景:
python
# utils.py
def helper():
return "help"
if __name__ == "__main__":
print("utils.py 被直接执行")
print(helper())python
# main.py
import utils
print(utils.helper()) # 输出 help
# 不会输出 "utils.py 被直接执行"被导入时,__name__ 的值是模块名 "utils"。直接执行时,__name__ 的值是 "__main__"。
这个机制让模块既能提供函数给别人用,又能包含自己的测试代码。
五、包
包是包含多个模块的目录,目录下必须有 __init__.py 文件(Python 3.3+ 非必须,但建议保留)。
text
myproject/
├── main.py
└── utils/
├── __init__.py
├── math_tools.py
└── string_tools.py__init__.py 可以是空文件,也可以包含包的初始化代码:
python
# utils/__init__.py
from .math_tools import circle_area
from .string_tools import normalize这样导入包时,这些名称自动可用:
python
from utils import circle_area, normalize相对导入
包内部模块之间的导入:
python
# utils/string_tools.py
from .math_tools import PI # 同一包内的模块
from ..config import settings # 上级目录的模块(需要是包结构)相对导入用 . 表示当前包,.. 表示上级包。相对导入只能在包内部使用,直接运行文件时会报错。
六、__all__
控制 from module import * 导入哪些名称:
python
# utils/math_tools.py
__all__ = ["circle_area", "circle_perimeter"]
PI = 3.14159 # 不会被 import * 导入
def circle_area(radius): ...
def circle_perimeter(radius): ...没有 __all__ 时,import * 导入所有不以 _ 开头的名称。有 __all__ 时,只导入列表中指定的名称。
七、重新加载模块
模块导入后会被缓存。修改模块文件后,已导入的模块不会自动更新:
python
import importlib
import utils
# 修改 utils.py 后重新加载
importlib.reload(utils)这个操作在交互式开发和调试时有用。生产代码中很少需要重新加载模块。
记忆锚点:一个 .py 文件是一个模块;import 导入,from...import 导入指定名称;不要和标准库模块同名;if __name__ == "__main__" 区分导入和直接执行;包是带 __init__.py 的目录;__all__ 控制 import * 的行为;模块导入后缓存,修改后需 importlib.reload()。