目录

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-secret

3. 依赖管理

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.txt

3.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 pytest

4. 日志与配置

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 wrapper

6. 部署上线

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 --build

6.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@v4

7.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. 练习题

  1. 将之前的 API 项目改造为 src 布局,使用 pyproject.toml 管理依赖。
  2. 为项目编写 Dockerfile 和 docker-compose.yml,实现一键部署。
  3. 配置 GitHub Actions,实现推送代码自动运行 lint 和测试。
  4. 使用 Makefile 整合 install、test、lint、format 命令。

下节预告:我们将梳理 Python 学习路线与推荐资源,为持续进阶指明方向。