Python 模块(Modules)
文章目录
我对“模块”的直觉理解
写代码时,总会把功能拆成多个文件。Python 里,一个以 .py 结尾的文件就是一个“模块”(module)。导入它,就能复用里面的函数、类和变量。模块让代码可分拆、可复用、可维护。
一句话:模块是“带名字的代码文件 + 独立命名空间”。
模块能解决什么问题
- 复用:写过的函数/类在别处直接
import使用。 - 命名隔离:不同文件(模块)有自己的命名空间,减少命名冲突。
- 组织结构:配合包(package),形成清晰的目录与层次。
模块的本质
- 一个
.py文件就是一个模块。 - 每个模块在加载时会创建一个独立的命名空间,模块名字符串存在
__name__。 - 已加载模块会被缓存到
sys.modules(单例缓存)。再次导入直接复用,避免重复执行。 - 首次导入时,Python 可能会在
__pycache__/写入编译缓存(.pyc)。
最小示例:创建与导入
先写一个文件 fibo.py:
# fibo.py
def fib(n: int) -> list[int]:
a, b = 0, 1
seq = []
while a < n:
seq.append(a)
a, b = b, a + b
return seq
print("fibo 模块已加载") # 导入时会执行顶层代码
在同一目录另写一个脚本:
# main.py
import fibo
print(fibo.fib(20))
运行 python main.py,会看到 fibo 模块已加载 的打印,以及结果序列。
几种常见导入方式:
import fibo # 推荐:命名空间清晰
import fibo as fb # 起别名
from fibo import fib # 直接引入对象名(注意命名污染)
from fibo import fib as f # 引入并重命名
一般更推荐 import 模块名 或 import 模块名 as 别名,可读性更好且不易冲突。
不推荐使用:
from fibo import * # 不清晰、易冲突、会覆盖同名标识
作为脚本运行 vs 被导入运行
模块被直接运行时,__name__ == '__main__';被别人导入时,__name__ 等于其模块名。常见写法:
# fibo.py
def fib(n: int) -> list[int]:
...
if __name__ == "__main__":
# 仅当直接执行 fibo.py 时运行
print(fib(20))
好处:既能当脚本跑自测,也能被安全导入不执行“入口逻辑”。
模块搜索路径(import 到底去哪找)
Python 导入模块时,会按顺序在 sys.path 里这些目录寻找:
- 当前脚本所在目录(或当前工作目录)
- 标准库路径
- 第三方库路径(通常是
site-packages) - 环境变量
PYTHONPATH指定的目录(若有)
可在代码里查看:
import sys
print(sys.path)
临时添加一个搜索路径:
import sys
sys.path.append("/path/to/dir")
更推荐通过项目结构或虚拟环境来管理搜索路径,而不是在代码里动态修改。
“编译”缓存(.pyc 与 pycache)
首次导入模块时,Python 会把字节码缓存在 __pycache__/模块名.版本标识.pyc 中,以加速下次启动。这是内部优化,一般无需手动干预。
重新加载模块(开发期热更新)
在交互式环境调试时,改了模块代码,想不重启就生效:
import importlib
import fibo
importlib.reload(fibo)
注意:reload 只在同一解释器进程内有效,且对已从模块“直接导入”的名字无效(例如 from fibo import fib 的 fib 名字不会随 reload 更新)。
包(Packages)与相对导入
当目录里包含多个模块时,可以组织成“包”:
mathkit/
__init__.py
stats.py
series/
__init__.py
fibo.py
- 目录带
__init__.py即被视为包;内部子目录同理。 - 导入:
import mathkit.stats
from mathkit import stats
from mathkit.series import fibo
在包内部可以使用相对导入:
# 在 mathkit/series/fibo.py 中
from .. import stats # 往上一层
from . import helper # 同一层
更推荐使用“绝对导入”,读起来最直观。相对导入适合包内模块间的本地引用。
补充:现代 Python 支持“命名空间包”(PEP 420),有时即使没有 __init__.py 也能把多个目录视作同一包,常见于复杂项目/安装场景。初学阶段可以先记住“有 __init__.py 最稳妥”。
__all__ 与导出控制
在模块或包的 __init__.py 中定义 __all__ 可控制 from x import * 的导出名单:
# __init__.py
__all__ = ["fib", "stats_mean"]
实际项目里不建议使用 import *,但 __all__ 仍可作为“公开 API 清单”来约定导出的接口。
后续会写一篇博客详细介绍这个 __all__
常见坑与避免方法
- 同名冲突:自己的模块名不要与标准库/第三方库重名(例如
random.py、sys.py)。 - 工作目录影响导入:在不同目录运行脚本,当前目录会进入
sys.path[0],可能导入到意想不到的模块。 - 循环导入:模块 A 导入 B,同时 B 又导入 A,容易在导入期就访问到“尚未定义”的对象。解决:
- 调整依赖结构,抽公共部分到第三个模块;
- 将导入语句移动到函数内部(延迟导入)。
- 顶层副作用:模块导入就打印/连接数据库/改全局状态,可能造成隐蔽问题。把“动作”放进
if __name__ == '__main__':或函数里。
组织项目的简单建议
project/
pyproject.toml or requirements.txt
src/
mypkg/
__init__.py
core.py
utils.py
tests/
test_core.py
- 把代码放在
src/下,测试用例单独放tests/。 - 使用绝对导入:
from mypkg import core。 - 入口脚本最少逻辑,主要做参数解析与调用库代码。
一个可运行的小例子(包 + 入口)
calc/
__init__.py
ops.py
main.py
calc/ops.py:
def add(a: float, b: float) -> float:
return a + b
def sub(a: float, b: float) -> float:
return a - b
main.py:
from calc import ops
def main() -> None:
print(ops.add(2, 3))
print(ops.sub(5, 1))
if __name__ == "__main__":
main()
运行:
python main.py
速查与最佳实践清单
- 创建模块:写一个
.py文件。 - 导入原则:优先
import 包.模块 as 别名;少用from x import y。 - 入口守卫:脚本入口用
if __name__ == '__main__':。 - 避免循环导入:抽公用、延迟导入、理清依赖方向。
- 别名与命名:避免与标准库/第三方同名;别名清晰(如
import numpy as np)。 - 结构化项目:使用包与清晰目录;尽量绝对导入。
- 调试 reload:
importlib.reload(mod)仅在交互开发临时使用。
进一步阅读
- Python 官方教程:Modules(标准模块、搜索路径、包等)
https://docs.python.org/3/tutorial/modules.html
这篇笔记到这儿,建议边读边敲,自己建几个小模块试一下,体会“模块 = 文件 + 命名空间”的感觉,基本就通了。
更多推荐



所有评论(0)