
接手过一个项目,根目录下 87 个 .py 文件,没有 init.py,没有 requirements.txt,README 写的是“跑不起来就 pip install -r requirements.txt”。
找了半小时才知道入口文件是哪个。
后来我定了一套项目结构规范,新同事接手基本 10 分钟能跑起来,不用问我。
往期阅读>>>
Python 自动化管理Jenkins的15个实用脚本,提升效率
App2Docker:如何无需编写Dockerfile也可以创建容器镜像
Python 自动化识别Nginx配置并导出为excel文件,提升Nginx管理效率
很多人觉得“代码能跑就行,文件放哪有关系吗”。
有关系,而且代价不小:
第一:新人接手成本
你写的时候知道哪个文件是入口,三个月后你自己都忘了。新人来了,翻半小时找不到 main.py,第一印象就差了。
第二:CI/CD 配不动
你的测试、打包、部署脚本,都依赖固定的目录结构。结构乱,CI 配置就得各种 hack,越写越难维护。
第三:包发布直接跪
要发 PyPI 的包,目录结构不对,build 的时候直接报错,或者发出去的包装不上。
这三件事,只要遇到一次,你就知道项目结构不是小事。
2024 年,Python 社区基本达成共识:用 src 布局。
my_project/├── src/│ └── my_package/│ ├── __init__.py│ ├── core.py│ ├── utils.py│ └── config.py├── tests/│ ├── __init__.py│ └── test_core.py├── docs/├── pyproject.toml├── README.md├── .gitignore└── LICENSE
为什么用 src 布局,而不是把 my_package/ 直接放根目录?
核心原因:避免“意外导入”
如果你的包目录在根目录,Python 会从当前目录找模块。你运行测试的时候,可能导入的是本地源码,而不是你以为 pip install 的那个版本。
用 src 布局,你的包代码不会跟根目录的其他文件混在一起,import 路径更清晰。
现在项目配置用 pyproject.toml,setup.py 是老黄历了。
[build-system]requires = ["hatchling"]build-backend = "hatchling.build"[project]name = "my-package"version = "0.1.0"description = "项目描述"readme = "README.md"license = {text = "MIT"}requires-python = ">=3.8"dependencies = ["requests>=2.28.0","httpx>=0.24.0",][project.optional-dependencies]dev = ["pytest>=7.0","black>=23.0","flake8>=6.0",][tool.pytest.ini_options]testpaths = ["tests"]
关键信息:
dependencies:生产环境依赖,pip install 的时候会自动装
optional-dependencies:开发依赖,别人 clone 项目后 pip install -e ".[dev]" 就能装齐开发工具
[tool.xxx]:各种工具的配置可以写在这里,不用单独维护 setup.cfg、pytest.ini 一堆文件
测试文件放哪?两个选择:
选择一:跟源码放一起
src/my_package/├── __init__.py├── core.py└── core_test.py
不推荐。测试代码跟业务代码混在一起,找起来费劲,而且发布包的时候容易把测试代码打进去。
选择二:单独 tests/ 目录(推荐)
tests/├── __init__.py├── test_core.py└── test_utils.py
运行测试:
# 在项目根目录执行pytest tests/
如果测试里需要导入你的包,记得先装成可编辑模式:
pip install -e .-e 是可编辑模式,改了源码不用重新安装,pytest 直接用最新的代码。
项目里经常有一堆配置文件:数据库连接、API密钥、日志配置……
不要直接写死在代码里:
# 别这样DB_HOST = "192.168.1.100"API_KEY = "sk-xxxxxxxx"
推荐用环境变量 + .env 文件:
# config.pyimportosfromdotenvimportload_dotenvload_dotenv() # 从 .env 文件加载环境变量DB_HOST = os.getenv("DB_HOST")API_KEY = os.getenv("API_KEY")
.env 文件(记得加到 .gitignore,别提交到 Git):
DB_HOST=192.168.1.100API_KEY=sk-xxxxxxxx
这样不同环境(开发/测试/生产)只需要改 .env 文件,不用动代码。
我最近做的一个数据处理项目,结构是这样的:
data_processor/├── src/│ └── data_processor/│ ├── __init__.py│ ├── main.py # 入口│ ├── extract.py # 数据提取│ ├── transform.py # 数据清洗│ ├── load.py # 数据入库│ └── utils/│ ├── __init__.py│ ├── logger.py # 日志配置│ └── db.py # 数据库连接├── tests/│ ├── __init__.py│ ├── test_extract.py│ └── test_transform.py├── scripts/ # 一次性脚本│ ├── migrate.py│ └── backfill.py├── docs/│ └── API.md├── pyproject.toml├── .env.example # 环境变量示例(不包含真实密钥)├── .gitignore├── README.md└── LICENSE
关键点:
scripts/ 放一次性脚本,跟核心代码分开,避免污染
.env.example 给新人参考,真实 .env 不提交
utils/ 内部再分模块,不要全堆在一个 utils.py 里
坑一:根目录堆了 100 个 .py 文件
你图省事,结果别人接手直接崩溃。花 10 分钟规划目录,后面省 10 小时。
坑二:requirements.txt 没锁版本
# 别这样requestshttpx
今天能跑,三个月后 requests 发了新版本,API 变了,你的代码跪了。
# 这样requests==2.31.0httpx==0.24.1
或者用 pyproject.toml 的 dependencies,配合 pip-tools 或 poetry 锁版本。
坑三:测试覆盖率 0%
不是要求你覆盖率 100%,但核心逻辑至少要有测试。不然你改一个函数,不知道哪里会崩。
项目结构这事,前期花 1 小时规划好,后面省无数时间。
几个关键点:
用 src 布局,避免意外导入
配置用 pyproject.toml,别用 setup.py
测试放单独 tests/ 目录
配置用环境变量,别写死代码里
一次性脚本放 scripts/,别跟核心代码混
你现在的项目结构是什么样的?有没有被烂结构坑过?评论区聊聊。
如果这篇文章帮到了你,转发给身边写 Python 的朋友,让他们也少踩点坑。
https://ima.qq.com/wiki/?shareId=f2628818f0874da17b71ffa0e5e8408114e7dbad46f1745bbd1cc1365277631c
