Python项目标准目录结构详解:从__init__.py到requirements.txt
深入解析现代Python项目的标准目录结构设计,从src布局架构、__init__.py包机制到requirements.txt依赖规范,帮助开发者构建清晰、可维护、易分发的工程化项目。
组织一个规范的 Python 项目并不只是把脚本放进同一个文件夹,而是要为代码导入、依赖隔离、自动化测试以及包分发建立确定性的规则。很多初学者在编写独立脚本时运转正常,但随着模块增多,往往会遭遇混乱的 ModuleNotFoundError、循环依赖导入错误或打包遗漏。本文将围绕现代 Python 项目的标准工程规范展开,从推行 src 架构的深层原因开始,逐一解析从核心包标识 __init__.py 到依赖清单 requirements.txt 的具体职责与实战配置。
一、现代 Python 项目的标准目录全貌:为什么推荐 src 布局?
在早期的 Python 教程中,常见一种“扁平结构”(Flat Layout),即直接将包文件夹与 tests、setup.py 放在项目根目录下。但现代 Python 官方打包规范(PyPA)极力推荐的是 src 布局(src-layout)。下方的树形结构展示了一个工程化标准的 Python 项目全貌:
my_project/
├── src/
│ └── my_project/ # 核心源码包
│ ├── __init__.py # 声明包命名空间与公开接口
│ ├── core.py # 核心业务逻辑模块
│ └── utils.py # 辅助工具模块
├── tests/ # 测试代码目录(与业务代码隔离)
│ ├── __init__.py # 测试包标识
│ ├── conftest.py # pytest 全局共享 fixtures
│ └── test_core.py # 单元测试用例
├── docs/ # 项目文档目录
│ └── index.md
├── .gitignore # Git 忽略规则文件
├── pyproject.toml # 现代构建系统与元数据配置(PEP 517/518/621)
├── requirements.txt # 部署环境生产依赖锁定清单
├── requirements-dev.txt # 本地开发与测试专用依赖
└── README.md # 项目介绍与使用指南
采用 src 布局的核心意义在于解决导入偏见(Import Parity)。在扁平结构中,当你在项目根目录下直接运行测试时,Python 解释器会将当前工作目录自动加入 sys.path 的首位。这会导致解释器优先导入当前源码目录下的代码,即使代码缺少必须的编译扩展、甚至没有正确安装打包,测试也能“侥幸通过”。而在 src 布局下,根目录下没有名为 my_project 的目录,必须先通过 pip install -e . 安装当前包后测试才能正常运行,从而确保了开发测试环境与最终用户的安装环境完全一致。
二、核心文件深度拆解一:__init__.py 的作用与最佳实践
在 Python 中,包含 __init__.py 的目录会被解释器视作一个常规包(Regular Package)。虽然自 Python 3.3(PEP 420)起引入了隐式命名空间包(Namespace Package),即使没有 __init__.py 目录也可以被导入,但对于绝大多数普通工程而言,显式保留 __init__.py 仍是黄金法则。
1. 它的核心职责是什么?
- 初始化包作用域:当外部首次执行
import my_project时,该文件内的顶层代码会被首先执行一次。 - 定义统一的公开接口(API Gateway):隐藏内部模块的复杂层级,将底层函数和类直接提升到包顶层,方便调用者引用。
- 明确导出白名单(
__all__):严格限制通过from my_project import *导出的变量和类,防止内部私有符号意外泄漏。
2. __init__.py 的标准编写范式
假设你的项目结构中有 core.py 和 utils.py,规范的 src/my_project/__init__.py 应该这样编写:
"""MyProject: 高性能工程化 Python 核心工具库."""
__version__ = "1.0.0"
# 从内部子模块导入需要公开给调用者的核心对象
from my_project.core import ProjectManager, TaskRunner
from my_project.utils import format_result
# 显式声明公开接口白名单
__all__ = [
"__version__",
"ProjectManager",
"TaskRunner",
"format_result",
]
避坑提醒:切勿在 __init__.py 中编写繁重的初始化逻辑(如发起网络请求、读取大型本地配置文件、创建数据库全局连接)。这会导致任何调用者即便仅需导入一个辅助常量,也会被迫触发沉重的冷启动开销。同时,过多的跨模块导入非常容易引发棘手的循环导入错误(Circular Import Error)。
三、核心文件深度拆解二:requirements.txt 与现代依赖管理
在交付或部署 Python 项目时,requirements.txt 是指引 pip 安装依赖的核心清单。然而,许多开发者习惯在终端直接执行 pip freeze > requirements.txt,这往往会导致开发机的临时调试包(如 IPython、Black 等)被无差别写入,造成生产环境体积臃肿与依赖冲突。
1. 依赖分层管理:生产环境与开发环境解耦
工业级实践中,建议将依赖拆分为两个独立文件:
requirements.txt(生产部署清单):只包含业务运行所必需的核心依赖,且必须显式锁定全量精确版本号(==),以保障生产部署的确定性与可复现性。requirements-dev.txt(开发测试清单):引入代码格式化、静态检查、测试框架等辅助工具,并通过-r requirements.txt继承生产依赖。
# requirements.txt 示例(生产运行依赖,精确版本锁死)
requests==2.31.0
pydantic==2.6.4
fastapi==0.110.0
uvicorn==0.29.0
# requirements-dev.txt 示例(本地开发环境)
-r requirements.txt
pytest==8.1.1
pytest-cov==4.1.0
ruff==0.3.4
mypy==1.9.0
2. requirements.txt 与 pyproject.toml 的分工
很多开发者会产生疑惑:既然现代标准推行 pyproject.toml,为什么还需要 requirements.txt?答案在于“抽象依赖声明”与“具体环境锁定”的区别:
| 对比维度 | pyproject.toml (PEP 621) | requirements.txt |
|---|---|---|
| 核心用途 | 定义项目的构建系统、元数据及抽象依赖范围。 | 具体运行环境(Docker、生产虚拟机)的依赖完全锁定。 |
| 版本约束风格 | 范围声明(例如 requests >= 2.28, < 3.0)。 |
精准锁定(例如 requests == 2.31.0)。 |
| 适用场景 | 发布为可分发的 Python Wheel/Tarball 库。 | 部署具体的业务终端应用或微服务系统。 |
四、操作指南:从零搭建标准 Python 工程四步法
理解了各项设计标准后,我们可以通过一套规范的操作流程,在本地终端完整搭建一个标准的 src 架构项目。
步骤 1:创建骨架目录与基础文件
在你的工作空间中执行以下命令(执行位置:终端宿主目录):
# 1. 创建工程根目录并进入
mkdir my_project && cd my_project
# 2. 创建 src 目录结构与测试目录
mkdir -p src/my_project tests
# 3. 创建核心文件
touch src/my_project/__init__.py src/my_project/core.py
touch tests/__init__.py tests/test_core.py
touch requirements.txt requirements-dev.txt pyproject.toml README.md .gitignore
步骤 2:配置现代构建规范 pyproject.toml
编辑 pyproject.toml,写入符合 PEP 621 规范的基础配置:
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my_project"
version = "0.1.0"
description = "一个标准的 Python 工程范例"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"requests>=2.31.0",
]
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
步骤 3:编写业务代码与单元测试
在 src/my_project/core.py 中实现一个简单的计算函数:
def calculate_tax(amount: float, rate: float = 0.05) -> float:
"""计算含税金额."""
if amount < 0:
raise ValueError("金额不能为负数")
return round(amount * (1 + rate), 2)
在 tests/test_core.py 中编写验证用例:
import pytest
from my_project.core import calculate_tax
def test_calculate_tax_normal():
assert calculate_tax(100.0, 0.05) == 105.0
def test_calculate_tax_negative():
with pytest.raises(ValueError, match="金额不能为负数"):
calculate_tax(-10.0)
步骤 4:安装并执行验证
在项目根目录下通过可编辑模式(Editable Mode)安装当前工程,并运行测试:
# 安装开发模式本地包
pip install -e .
# 安装开发测试依赖
pip install pytest
# 运行自动化测试
pytest
终端预期输出(示例):
============================== test session starts ==============================
platform darwin -- Python 3.10.12, pytest-8.1.1, pluggy-1.4.0
rootdir: /Users/username/my_project
configfile: pyproject.toml
testpaths: tests
collected 2 items
tests/test_core.py .. [100%]
=============================== 2 passed in 0.03s ===============================
五、避坑指南:四大高频目录与依赖误区排查
误区 1:直接在 src/ 内部运行脚本
很多开发者习惯在终端 cd src/my_project/ 然后运行 python core.py。这会导致解释器将 src/my_project/ 作为搜寻根目录,从而引发 ModuleNotFoundError: No module named 'my_project'。
修复方法:永远在项目根目录下,使用模块化参数运行:python -m my_project.core,或通过 pip install -e . 完成本地挂载后运行。
误区 2:在 .gitignore 中遗漏缓存目录导致污染 Git
Python 运行时会自动在每个包目录下生成 __pycache__/ 目录和 .pyc 字节码文件,虚拟环境目录(如 .venv/)和 pytest 缓存(.pytest_cache/)极易被误提交。
修复方法:在根目录 .gitignore 中务必添加:
__pycache__/、*.py[cod]、.venv/、.pytest_cache/、dist/、build/、*.egg-info/。
误区 3:在 requirements.txt 中过度通配或不锁间接依赖
只写 requests 而不写版本号,当上游依赖发版引入不兼容变更时,CI/CD 流水线或生产 Docker 镜像重新构建会突然崩溃。
修复方法:生产交付请务必锁定固定版本号,推荐配合 pip-tools(pip-compile)自动化生成全量锁定的 requirements.txt。
误区 4:测试文件命名不规范导致 pytest 无法发现用例
pytest 默认通过正则匹配发现用例。如果测试脚本命名为 core_test.py 或类名未大写 Test*,pytest 会直接跳过。
修复方法:测试文件统一以 test_*.py 或 *_test.py 命名,测试函数统一以 test_*() 命名。
六、全景总结:企业级 Python 项目工程资产速查表
为了让读者在实际开发和代码评审时能够快速对照检查,下方的资产速查图与检查表汇总了现代 Python 项目从开发源码到交付分发的全景规范:
工程化落地核对清单
- 结构 源码位于
src/<package_name>/目录,避免采用直接平铺在根目录的扁平结构。 - 包 核心包及其包含 Python 源码的子目录中均具有明确的
__init__.py文件。 - 导出
__init__.py中定义了__all__白名单,且未包含重度阻塞型网络/磁盘 IO 逻辑。 - 测试 测试用例存放于根目录
tests/,遵循test_*.py命名,并通过pytest执行。 - 依赖 生产环境
requirements.txt锁死==,开发工具单独放入requirements-dev.txt。 - 配置 根目录下提供
pyproject.toml统一声明构建后端及代码分析工具参数。 - 洁净
.gitignore已正确配置,确保字节码文件与虚拟环境不会进入版本控制。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













