Python 项目最佳实践
目录
学习目标:掌握 Python 项目的工程化最佳实践,涵盖目录结构、配置管理、依赖锁定、部署上线与 CI/CD。
1. 项目目录结构
1.1 标准结构
my_project/
├── pyproject.toml # 项目元数据与依赖(现代标准)
├── requirements.txt # 依赖锁定(兼容传统工具)
├── README.md # 项目说明
├── .gitignore # Git 忽略规则
├── .env.example # 环境变量示例
├── Makefile # 常用命令快捷方式
├── src/ # 源码目录
│ └── my_package/
│ ├── __init__.py
│ ├── core/ # 核心逻辑
│ ├── models/ # 数据模型
│ ├── services/ # 业务服务
│ └── utils/ # 工具函数
├── tests/ # 测试目录
│ ├── conftest.py # pytest 固件
│ ├── unit/ # 单元测试
│ └── integration/ # 集成测试
├── docs/ # 文档
└── scripts/ # 脚本工具1.2 使用 src 布局的好处
# src 布局强制安装后才可导入,避免测试误用本地路径
# 传统布局(不推荐)
my_package/
__init__.py
tests/
test_xxx.py # 可能直接 import my_package(未安装状态)
# src 布局(推荐)
src/
my_package/
__init__.py
tests/
test_xxx.py # 必须先 pip install -e . 才能导入2. 项目配置
2.1 pyproject.toml(推荐)
# pyproject.toml
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "my-package"
version = "1.0.0"
description = "我的 Python 项目"
requires-python = ">=3.10"
dependencies = [
"requests>=2.31",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-cov>=4.0",
"ruff>=0.1",
"mypy>=1.5",
]
[project.scripts]
my-tool = "my_package.cli:main"
# ruff 配置
[tool.ruff]
line-length = 100
target-version = "py310"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W"]
# pytest 配置
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --cov=my_package --cov-report=term-missing"2.2 环境变量管理
# config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
"""应用配置(从环境变量读取)"""
app_name: str = "My App"
debug: bool = False
database_url: str = "sqlite:///./app.db"
secret_key: str = "change-me"
api_timeout: int = 30
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
@lru_cache
def get_settings() -> Settings:
"""单例配置(缓存)"""
return Settings()
# 使用
settings = get_settings()
print(settings.database_url)# .env
DEBUG=true
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
SECRET_KEY=your-production-secret3. 依赖管理
3.1 使用 pip-tools 锁定依赖
# 安装
pip install pip-tools
# 编写 requirements.in(顶层依赖)
# requirements.in
fastapi
uvicorn[standard]
sqlalchemy
# 编译生成锁定的 requirements.txt
pip-compile requirements.in -o requirements.txt
# 安装精确版本
pip-sync requirements.txt3.2 使用 uv(高性能替代)
# 安装 uv(Rust 编写,极快)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 创建项目
uv init my_project
cd my_project
# 添加依赖
uv add fastapi uvicorn sqlalchemy
uv add --dev pytest ruff mypy
# 同步环境
uv sync
# 运行
uv run pytest4. 日志与配置
4.1 结构化日志
# logging_config.py
import logging
import sys
from pathlib import Path
def setup_logging(log_file: str = "app.log", level: str = "INFO"):
"""配置日志"""
log_format = "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s"
date_format = "%Y-%m-%d %H:%M:%S"
# 控制台 + 文件双输出
logging.basicConfig(
level=level,
format=log_format,
datefmt=date_format,
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler(log_file, encoding='utf-8'),
],
)
# 降低第三方库日志级别
logging.getLogger("urllib3").setLevel(logging.WARNING)
# 使用
logger = logging.getLogger(__name__)
setup_logging()
logger.info("服务启动")
logger.error("数据库连接失败", exc_info=True)4.2 使用 loguru(更简洁)
from loguru import logger
# 自动带颜色、时间、文件位置
logger.info("用户登录: user_id={}", 123)
# 输出到文件并自动轮转
logger.add("app.log", rotation="10 MB", retention="7 days", encoding="utf-8")
# JSON 格式(便于日志收集)
logger.add("app.json", serialize=True)
try:
1 / 0
except Exception:
logger.exception("计算失败")5. 错误处理架构
# errors.py
from enum import Enum
from dataclasses import dataclass
class ErrorCode(Enum):
SUCCESS = 0
PARAM_ERROR = 1001
NOT_FOUND = 1002
UNAUTHORIZED = 1003
SERVER_ERROR = 5000
@dataclass
class AppError(Exception):
"""应用统一异常"""
code: ErrorCode
message: str
def __post_init__(self):
super().__init__(self.message)
# 业务中使用
def get_user(user_id: int, db):
user = db.query(User).get(user_id)
if not user:
raise AppError(ErrorCode.NOT_FOUND, f"用户 {user_id} 不存在")
return user
# 统一捕获
def handle_errors(func):
"""错误处理装饰器"""
from functools import wraps
@wraps(func)
def wrapper(*args, **kwargs):
try:
result = func(*args, **kwargs)
return {"code": 0, "message": "success", "data": result}
except AppError as e:
return {"code": e.code.value, "message": e.message, "data": None}
except Exception as e:
logger.exception("未预期错误")
return {"code": 5000, "message": "服务器错误", "data": None}
return wrapper6. 部署上线
6.1 Docker 容器化
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
# 安装依赖(利用缓存层)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 拷贝源码
COPY src/ ./src/
COPY pyproject.toml .
# 安装项目
RUN pip install -e .
EXPOSE 8000
CMD ["uvicorn", "my_package.main:app", "--host", "0.0.0.0", "--port", "8000"]# docker-compose.yml
version: "3.8"
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
pgdata:# 构建并启动
docker-compose up -d --build6.2 Gunicorn + Uvicorn 生产部署
# 安装
pip install gunicorn uvicorn[standard]
# 多 worker 部署
gunicorn my_package.main:app \
-w 4 \
-k uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 120 \
--access-logfile - \
--error-logfile -7. CI/CD 自动化
7.1 GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: 安装 Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: 安装依赖
run: |
pip install -e ".[dev]"
- name: 代码检查
run: |
ruff check src/ tests/
mypy src/
- name: 运行测试
run: pytest --cov
- name: 上传覆盖率
uses: codecov/codecov-action@v47.2 Makefile 快捷命令
# Makefile
.PHONY: install test lint format type-check clean
install:
pip install -e ".[dev]"
test:
pytest -v --cov
lint:
ruff check src/ tests/
format:
ruff format src/ tests/
ruff check --fix src/ tests/
type-check:
mypy src/
clean:
find . -type d -name __pycache__ -exec rm -rf {} +
find . -type d -name .pytest_cache -exec rm -rf {} +
rm -rf .coverage htmlcov dist build *.egg-info
make install # 安装
make test # 测试
make lint # 检查
make format # 格式化8. 性能优化要点
# 1. 使用 __slots__ 减少内存
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
# 2. 使用生成器处理大数据
def read_large_file(path):
with open(path) as f:
for line in f:
yield line.strip()
# 3. 字符串拼接用 join
parts = ['a', 'b', 'c']
result = ''.join(parts) # 比 += 快
# 4. 使用局部变量加速循环
def process(data):
append = result.append # 局部引用
for item in data:
append(item * 2)
# 5. 缓存计算结果
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)9. 小结
| 维度 | 推荐方案 |
|---|---|
| 项目结构 | src 布局 + pyproject.toml |
| 依赖管理 | uv / pip-tools 锁定版本 |
| 配置管理 | pydantic-settings + .env |
| 日志 | loguru 或标准 logging |
| 代码质量 | ruff + mypy + pytest |
| 容器化 | Docker + docker-compose |
| 生产部署 | Gunicorn + Uvicorn |
| CI/CD | GitHub Actions |
10. 练习题
- 将之前的 API 项目改造为 src 布局,使用 pyproject.toml 管理依赖。
- 为项目编写 Dockerfile 和 docker-compose.yml,实现一键部署。
- 配置 GitHub Actions,实现推送代码自动运行 lint 和测试。
- 使用 Makefile 整合 install、test、lint、format 命令。
下节预告:我们将梳理 Python 学习路线与推荐资源,为持续进阶指明方向。