2. Python后端项目结构规范化
很多Python后端项目最初都从一个 app.py 或 main.py 开始:路由、数据库连接、配置、业务逻辑全部堆在一个文件里。当代码量超过几百行、需要多人协作、需要写单元测试、需要区分开发与生产环境时,这种“脚本式”结构就会迅速失控。本节的目标,是把一个单文件脚本重构为可测试、可部署、配置分层的标准工程结构,并理解每一步背后的工程动机。
一、脚本式结构为什么会失效
假设有一个最简的 Flask 脚本:
# app.py(反例)
from flask import Flask
import pymysql
app = Flask(__name__)
DB = pymysql.connect(host="localhost", user="root", password="123456", db="demo")
@app.route("/users")
def users():
cur = DB.cursor()
cur.execute("SELECT id, name FROM users")
return {"data": cur.fetchall()}
if __name__ == "__main__":
app.run(debug=True)
这段代码能跑,但存在四个硬伤:配置硬编码(数据库密码写死在源码里,无法区分环境);副作用发生在导入时(import app 就会尝试连数据库,测试时无法隔离);没有依赖声明(换一台机器不知道要装 flask 和 pymysql 的哪个版本);无法测试(路由函数与数据库强耦合,没有可替换的边界)。
二、规范化后的目录结构
一个可测试、可部署的 Python 后端工程,通常采用如下布局(以 Flask 为例,FastAPI/Django 思路一致):
backend-demo/
├── pyproject.toml # 项目元数据 + 依赖声明
├── requirements.txt # 锁定版本(可选,用于部署)
├── .env.example # 环境变量样例(不含真实密钥)
├── .gitignore
├── src/
│ └── backend_demo/
│ ├── __init__.py # 应用工厂 create_app()
│ ├── config.py # 配置分层
│ ├── api/
│ │ ├── __init__.py
│ │ └── users.py # 路由蓝图
│ ├── services/
│ │ └── user_service.py # 业务逻辑
│ ├── repositories/
│ │ └── user_repo.py # 数据访问
│ └── models/
│ └── user.py
└── tests/
├── conftest.py
└── test_users.py
这个结构的关键不是“目录多”,而是每一层都有明确职责和可替换边界:api 只负责解析请求和返回响应,services 承载业务规则,repositories 封装数据库访问,config 集中管理配置。测试时可以只替换 repository,而不必启动真实数据库。
三、配置分层:让同一份代码跑在不同环境
配置分层的核心思想是:代码不变,环境变量变。常见做法是定义一个基类配置,再按环境派生:
# src/backend_demo/config.py
import os
class BaseConfig:
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret")
DB_HOST = os.environ.get("DB_HOST", "localhost")
DB_PORT = int(os.environ.get("DB_PORT", 3306))
DB_NAME = os.environ.get("DB_NAME", "demo")
DB_USER = os.environ.get("DB_USER", "root")
DB_PASSWORD = os.environ.get("DB_PASSWORD", "")
class DevConfig(BaseConfig):
DEBUG = True
class TestConfig(BaseConfig):
TESTING = True
DB_NAME = "demo_test" # 测试库独立
DB_PASSWORD = "test"
class ProdConfig(BaseConfig):
DEBUG = False
CONFIG_MAP = {"dev": DevConfig, "test": TestConfig, "prod": ProdConfig}
def get_config(env: str = None):
env = env or os.environ.get("APP_ENV", "dev")
return CONFIG_MAP[env]
这样,本地开发用 APP_ENV=dev,CI 跑测试用 APP_ENV=test,生产部署用 APP_ENV=prod,同一份代码通过环境变量切换行为。真实密钥通过 .env 文件或部署平台的 Secret 注入,.env.example 只保留字段名和占位值,提交到仓库供他人参考。
四、应用工厂:消除导入副作用
把 app = Flask(__name__) 从模块顶层移入工厂函数,是让项目可测试的关键一步:
# src/backend_demo/__init__.py
from flask import Flask
from .config import get_config
from .api.users import users_bp
def create_app(config=None):
app = Flask(__name__)
app.config.from_object(config or get_config())
app.register_blueprint(users_bp, url_prefix="/api")
return app
此时 import backend_demo 不会连接数据库,也不会启动服务。测试代码可以传入 TestConfig 创建独立实例:
# tests/conftest.py
import pytest
from backend_demo import create_app
from backend_demo.config import TestConfig
@pytest.fixture
def client():
app = create_app(TestConfig)
with app.test_client() as c:
yield c
五、依赖声明与环境隔离
依赖声明解决“换台机器跑不起来”的问题。推荐在 pyproject.toml 中声明直接依赖及其版本范围:
[project]
name = "backend-demo"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"flask>=3.0,<4.0",
"pymysql>=1.1,<2.0",
]
[project.optional-dependencies]
dev = ["pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.5"]
环境隔离则通过虚拟环境实现。标准流程是:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]" # 安装项目本身 + 开发依赖
pip freeze > requirements.txt # 锁定精确版本,供部署使用
注意区分两类文件:pyproject.toml 声明“需要什么”,requirements.txt 锁定“实际装了什么版本”。前者用于开发,后者用于部署复现。
pytest 跑通至少一个针对路由的测试?如果能,说明配置分层、应用工厂和依赖边界都已到位。
六、从结构到可部署
规范化结构最终服务于部署。部署时通常只需要三样东西:代码(src/)、依赖锁定(requirements.txt)、环境变量(APP_ENV、DB_*、SECRET_KEY)。启动命令也从 python app.py 变为通过 WSGI 服务器加载工厂:
gunicorn "backend_demo:create_app()" -b 0.0.0.0:8000 -w 4