知识点思维导图
28 个知识节点
参考资料
Python(30) - 项目结构与规范
读完后,你应能完成以下任务:
- 绘制“Python(30) - 项目结构与规范 / 先建立直觉:Python 项目 ≈ 前端项目的目录组织”的关键对象与数据流,解释“边界(这里和前端不一样):前端的「依赖隔离」是自动的——每个项目自带 node_modules,你几乎不用操心。”,并用源码位置、日志或 Trace 标注证据。
- 为“Python(30) - 项目结构与规范 / 目录组织:一个标准 Python 项目长什么样”设计正常与异常输入,验证“新手最容易把所有 .py 文件平铺在根目录,跑是能跑,但项目一大就乱。”,输出首个偏差位置与回归测试结果。
- 实现“Python(30) - 项目结构与规范 / 配置管理:别把配置写死在代码里”的最小代码或配置,检验“边界:pyproject.toml 用的是 TOML 格式,不是 JSON——别习惯性写大括号和逗号。”,输出命令、结果与 Diff,并说明不适用边界。
前端项目你闭着眼都能搭:
src/放代码、package.json管依赖、.env放配置、出问题翻控制台。Python 这套东西全都有,但叫法和习惯不一样——目录怎么分层、配置往哪放、密钥怎么不进 git、日志为什么不能用
一、先建立直觉:Python 项目 ≈ 前端项目的目录组织
类比:你脑子里前端项目的样子——根目录一堆配置文件(package.json、.env、.gitignore),源码全塞 src/,依赖装进 node_modules/——这套心智模型几乎可以原样搬到 Python。每一块都有对应物:
| 前端 | Python | 作用 |
|---|---|---|
src/ |
src/包名/ 或 包名/ |
源码主目录 |
package.json |
pyproject.toml |
项目元信息 + 依赖声明 |
node_modules/ |
venv/(虚拟环境) |
第三方依赖装这里 |
.env |
.env |
环境变量 / 密钥 |
.gitignore |
.gitignore |
忽略不进库的文件 |
index.js(入口) |
main.py / __main__.py |
程序入口 |
console.log |
logging 模块 |
日志输出 |
tests/ |
tests/ |
测试代码(见第 31 篇) |
README.md |
README.md |
项目说明 |
边界(这里和前端不一样):前端的「依赖隔离」是自动的——每个项目自带 node_modules,你几乎不用操心。Python 默认所有项目共用一套全局解释器,不手动建虚拟环境就会版本打架(详见第 12 篇)。所以 Python 项目的「规范」第一条永远是:进项目先激活 venv。这点心智负担是前端没有的。
二、目录组织:一个标准 Python 项目长什么样
新手最容易把所有 .py 文件平铺在根目录,跑是能跑,但项目一大就乱。社区有约定俗成的结构。先看一个典型的中小项目(以 FastAPI 后端为例):
myproject/
├── pyproject.toml # 项目元信息 + 依赖(≈ package.json)
├── README.md # 项目说明
├── .gitignore # git 忽略清单
├── .env # 本地配置/密钥(绝不进 git)
├── .env.example # 配置模板(进 git,标明需要哪些变量,但不填真值)
│
├── src/ # 源码根(src layout,下面解释为什么用它)
│ └── myproject/ # 真正的包,包名 = 项目名
│ ├── __init__.py # 让 myproject 成为一个包(≈ index 入口)
│ ├── main.py # 程序入口
│ ├── config.py # 配置集中管理
│ ├── api/ # 路由层(子包)
│ │ ├── __init__.py
│ │ └── users.py
│ ├── services/ # 业务逻辑层
│ │ └── __init__.py
│ ├── models/ # 数据模型
│ │ └── __init__.py
│ └── utils/ # 工具函数
│ └── __init__.py
│
├── tests/ # 测试(和 src 平级,见第 31 篇)
│ └── test_users.py
│
└── logs/ # 日志输出目录(一般 gitignore 掉)
这套分层(api / services / models)和你在后端见过的 MVC 三层是一个思路:路由只管收发请求,业务逻辑收在 services,数据结构放 models。
2.1 src layout vs flat layout
你会看到两种摆法,区别只在「包要不要再套一层 src/」:
# flat layout(扁平)—— 包直接放根目录
myproject/
├── pyproject.toml
└── myproject/ # 包和配置文件同级
└── __init__.py
# src layout(推荐)—— 包放进 src/
myproject/
├── pyproject.toml
└── src/
└── myproject/ # 包多套一层 src
└── __init__.py
为什么推荐 src layout(讲 WHY 而非 WHAT):flat 布局下,你在根目录运行测试时,Python 会因为「当前目录在 sys.path 里」(详见第 8 篇)而直接 import 到源码目录,哪怕你根本没安装这个包。这会掩盖「忘了声明某个依赖」之类的打包问题——本地跑得好好的,装到别人机器上就崩。src layout 强制你把包真正安装一遍(pip install -e .,类似前端的 npm link)才能 import,测的就是「用户实际拿到的东西」,把问题暴露在自己机器上。
边界:小脚本、一次性项目用 flat 完全没问题,别上来就搞 src 把自己绕晕。要发布成库、或者项目会长期维护,再上 src layout。
三、配置管理:别把配置写死在代码里
前端你早就知道「配置和代码分离」:API 地址、密钥放 .env,用 import.meta.env 或 process.env 读。Python 完全一样的思路,工具不同而已。
3.1 项目元信息:pyproject.toml(≈ package.json)
现代 Python 项目的「中央配置文件」,声明项目名、版本、依赖,连各种工具(格式化、测试)的配置都能塞进去:
# pyproject.toml —— 角色等于前端的 package.json
[project]
name = "myproject" # 项目名
version = "0.1.0" # 版本号(≈ package.json 的 version)
requires-python = ">=3.10" # 要求的 Python 版本(≈ engines.node)
dependencies = [ # 运行时依赖(≈ dependencies)
"fastapi>=0.110",
"pydantic-settings>=2.0",
"python-dotenv>=1.0",
]
[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff"] # 开发依赖(≈ devDependencies)
边界:pyproject.toml 用的是 TOML 格式,不是 JSON——别习惯性写大括号和逗号。它和老式的 requirements.txt 不冲突:requirements.txt 是「锁死的扁平清单」(≈ lockfile 的简化版),pyproject.toml 是「带元信息的依赖声明」(≈ package.json 本体)。详见第 12 篇。
3.2 环境变量与密钥:.env + python-dotenv
和前端一模一样:敏感信息(数据库密码、API key)放 .env,.env 进 .gitignore,只把不含真值的 .env.example 提交。
# .env —— 本地真实配置,绝不进 git
DATABASE_URL=postgresql://user:secret@localhost/mydb
OPENAI_API_KEY=sk-真实密钥
DEBUG=true
读取方式有两种。最朴素的是 python-dotenv + os.getenv,几乎是前端 dotenv 的翻版:
并排看前端,思路完全一致:
// 前端 / node
import 'dotenv/config' // 加载 .env 到 process.env
const dbUrl = process.env.DATABASE_URL ?? 'sqlite:///./local.db'
3.3 推荐做法:用 pydantic-settings 做「类型安全的配置」
os.getenv 的毛病你在前端也踩过:取出来全是字符串,"true" 不是布尔,少配一个变量要运行到那行才报错。Python 的 pydantic-settings 能把配置变成一个带类型校验的配置对象(类似你用 zod 校验过的 config),启动时就把类型转好、缺失项报出来。
为什么比裸 os.getenv 好:配置项集中在一个类里一目了然;类型自动转换("8000"→8000、"true"→True);必填项缺失会在启动瞬间报错,而不是跑到用它的那行才崩——这正是你用 TS/zod 想要的「尽早暴露问题」。
边界:字段名默认不区分大小写地匹配环境变量(database_url 字段匹配 DATABASE_URL),这点和你手写 os.getenv("DATABASE_URL") 大小写敏感不同,别被绕到。
四、日志:为什么不能用 print
新手最大的坏习惯:调试全靠 print。前端你早就从 console.log 进化到分级日志(console.warn / console.error,或者 pino / winston)了,Python 也有标准的 logging 模块,生产代码一律用它,别用 print。
4.1 print 和 logging 的差别(讲 WHY)
print 的问题不是「不能用」,而是:不能分级别(没法只看错误、屏蔽调试信息)、没有时间戳/来源、不能统一改输出去向(控制台 / 文件 / 远程)、上线后想关掉得逐个删。logging 把这些全解决了——这和你不会在生产代码里留一堆 console.log 是同一个道理。
| console(前端) | Python logging | 级别含义 |
|---|---|---|
console.debug |
logger.debug() |
最啰嗦的调试细节 |
console.log / info |
logger.info() |
正常流程信息 |
console.warn |
logger.warning() |
警告,没崩但要注意 |
console.error |
logger.error() |
出错了 |
| (无) | logger.critical() |
致命错误 |
4.2 基本用法
输出长这样(自带时间、来源、级别,print 给不了):
2026-06-11 10:30:00,123 [myproject.api.users] INFO: 开始创建用户: imber
2026-06-11 10:30:00,124 [myproject.api.users] INFO: 用户创建成功: imber
4.3 写到文件 / 滚动日志
生产环境日志要落盘,且要防止单文件无限变大。用 logging.handlers:
边界 1(高频踩坑):logging.basicConfig 只在根日志器尚未配置时生效一次,第二次调用默认被忽略。很多人「日志配置没生效」就是因为别处(或某个库)已经先配过了。要么集中在程序入口配一次,要么用 force=True 强制覆盖。
边界 2:日志参数用 logger.info("用户 %s 登录", name) 这种占位符 + 参数的写法,别用 f-string(f"用户 {name} 登录")。WHY:占位符写法只有在该级别真要输出时才做字符串拼接,DEBUG 级别被过滤时省掉拼接开销;这是 logging 的设计约定,和 print 直接传字符串不同。
五、入口文件:让项目「能被跑起来」
前端 package.json 里 "main": "index.js" 或 scripts 指定入口。Python 常见两种:
# 对应两种运行方式
python src/myproject/main.py # 直接跑脚本
python -m myproject # 以模块方式跑包(推荐,包路径解析更可靠,详见第 8 篇)
六、.gitignore:哪些东西绝不能进 git
前端你知道不提交 node_modules 和 .env。Python 要忽略的东西类似,但多了些 Python 特有的:
# 虚拟环境(≈ node_modules,体积大、可重建,绝不进库)
venv/
.venv/
# Python 编译缓存(运行时自动生成的字节码,无需提交)
__pycache__/
*.pyc
# 配置与密钥(≈ 前端的 .env,含敏感信息)
.env
# 日志、本地数据库
logs/
*.log
*.sqlite3
# IDE / 测试缓存
.idea/
.pytest_cache/
边界:__pycache__/ 和 .pyc 是 Python 运行时自动生成的字节码缓存(CPython 把 .py 编译成字节码缓存下来加速下次启动),前端没有完全对应物——别看到陌生就去提交它,加进 .gitignore 忽略即可。
七、常见踩坑清单
- 所有
.py平铺在根目录:小脚本无所谓,项目一大必乱。按api / services / models / utils分层,每个目录记得放__init__.py(详见第 8 篇)。 - 密钥硬编码进代码、甚至提交进 git:API key、数据库密码一律走
.env,.env必须.gitignore,只提交.env.example模板。已经误提交的密钥要当作泄露处理(改掉它)。 - 生产代码用 print 当日志:换成
logging,分级别、带时间来源、可统一改去向。 logging.basicConfig配了没生效:它只生效一次。集中在入口配,或用force=True。- 配置散落各处
os.getenv:集中到一个config.py的Settings类(pydantic-settings),类型安全、缺失早报错。 - src layout 下 import 不到自己的包:src 布局需要先
pip install -e .把包装成可编辑模式(≈npm link)才能 import,这是它的特性不是 bug。
八、总结
- 先建立直觉:Python 项目 ≈ 前端项目的目录组织:| console.log | logging 模块 | 日志输出 |
- 目录组织:一个标准 Python 项目长什么样:新手最容易把所有 .py 文件平铺在根目录,跑是能跑,但项目一大就乱。
- 配置管理:别把配置写死在代码里:边界:pyproject.toml 用的是 TOML 格式,不是 JSON——别习惯性写大括号和逗号。
- 日志:为什么不能用 print:print 的问题不是「不能用」,而是:不能分级别(没法只看错误、屏蔽调试信息)、没有时间戳/来源、不能统一改输出去向(控制台 / 文件 / 远程)、上线后想关掉得逐个删。
- 入口文件:让项目「能被跑起来」:前端 package.json 里 "main": "index.js" 或 scripts 指定入口。
- .gitignore:哪些东西绝不能进 git:边界:pycache/ 和 .pyc 是 Python 运行时自动生成的字节码缓存(CPython 把 .py 编译成字节码缓存下来加速下次启动),前端没有完全对应物——别看到陌生就去提交它,加进 .gitignore 忽略即可。
学完自测
选择所有正确答案;提交后逐项核对判断依据。