Administrator
发布于 2026-03-19 / 0 阅读
0
0

第十二章:实战项目建议

第十二章:实战项目建议

本章学习目标

  • 理解项目开发流程
  • 掌握项目结构组织
  • 学会编写测试用例
  • 了解代码规范和文档
  • 独立完成一个完整项目

12.1 Python 项目结构

12.1.1 为什么要使用项目结构

良好的项目结构可以:

  • 代码组织清晰,易于维护
  • 方便团队协作
  • 易于测试和部署
  • 符合 Python 社区惯例

12.1.2 典型项目结构

my_project/                      # 项目根目录
├── src/                         # 源代码目录
│   ├── __init__.py              # 包初始化
│   ├── module1.py               # 功能模块1
│   ├── module2.py               # 功能模块2
│   └── sub_package/             # 子包
│       ├── __init__.py
│       └── module3.py
├── tests/                       # 测试目录
│   ├── __init__.py
│   ├── test_module1.py
│   └── test_module2.py
├── docs/                        # 文档目录
├── scripts/                      # 脚本目录
├── data/                        # 数据目录
├── notebooks/                   # Jupyter notebooks
├── requirements.txt             # 依赖列表
├── setup.py                     # 安装配置
├── pyproject.toml               # 现代项目配置
├── .gitignore                   # Git 忽略文件
├── .editorconfig                # 编辑器配置
├── README.md                    # 项目说明
└── LICENSE                      # 许可证

12.1.3 源代码目录选择

# 方式1:src 目录(推荐)
src/
    my_package/
        __init__.py

# 方式2:直接使用包
my_package/
    __init__.py

12.2 pyproject.toml 配置

12.2.1 完整配置示例

[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my_project"
version = "1.0.0"
description = "项目描述"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
    {name = "Your Name", email = "you@example.com"}
]
keywords = ["python", "example"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
]

dependencies = [
    "requests>=2.28.0",
    "numpy>=1.24.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0.0",
    "black>=22.0.0",
    "ruff>=0.1.0",
    "mypy>=0.950",
]
test = [
    "pytest>=7.0.0",
    "pytest-cov>=4.0.0",
]
docs = [
    "sphinx>=6.0.0",
]

[project.scripts]
my_project = "my_project.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

[tool.black]
line-length = 88
target-version = ["py310"]
include = '\.pyi?$'

[tool.ruff]
line-length = 88
select = ["E", "F", "W", "I"]
ignore = ["E501"]

[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_functions = "test_*"

12.3 虚拟环境和依赖管理

12.3.1 创建虚拟环境

# 使用 venv
python -m venv venv

# 激活(Windows)
venv\Scripts\activate
# 或 PowerShell
venv\Scripts\Activate.ps1

# 激活(macOS/Linux)
source venv/bin/activate

# 退出
deactivate

12.3.2 安装依赖

# 从 requirements.txt 安装
pip install -r requirements.txt

# 从 pyproject.toml 安装
pip install -e .

# 开发模式安装
pip install -e ".[dev]"

# 安装所有可选依赖
pip install -e ".[dev,test,docs]"

12.3.3 requirements.txt

# 核心依赖
requests>=2.28.0
numpy>=1.24.0
pandas>=2.0.0

# 开发依赖
pytest>=7.0.0
black>=22.0.0
ruff>=0.1.0
mypy>=0.950

12.4 完整项目示例:命令行 Todo 应用

12.4.1 项目结构

todo_app/
├── src/
│   └── todo/
│       ├── __init__.py
│       ├── models.py
│       ├── storage.py
│       └── cli.py
├── tests/
│   ├── __init__.py
│   └── test_todo.py
├── pyproject.toml
└── README.md

12.4.2 数据模型

# src/todo/models.py
"""数据模型"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal


@dataclass
class TodoItem:
    """待办事项"""
    id: int
    title: str
    completed: bool = False
    priority: Literal["low", "medium", "high"] = "medium"
    created_at: datetime = field(default_factory=datetime.now)
    completed_at: datetime | None = None

    def complete(self) -> None:
        """标记为完成"""
        self.completed = True
        self.completed_at = datetime.now()

    def uncomplete(self) -> None:
        """标记为未完成"""
        self.completed = False
        self.completed_at = None

    def __str__(self) -> str:
        status = "✓" if self.completed else "✗"
        priority_symbol = {"low": "↓", "medium": "→", "high": "↑"}
        return f"[{status}] {priority_symbol[self.priority}] {self.title}"

12.4.3 存储层

# src/todo/storage.py
"""存储层"""
import json
from pathlib import Path
from typing import Literal

from .models import TodoItem


class Storage:
    """JSON 文件存储"""

    def __init__(self, filepath: str = "todos.json") -> None:
        self.filepath = Path(filepath)
        self._todos: dict[int, TodoItem] = {}
        self._load()

    def _load(self) -> None:
        """加载数据"""
        if self.filepath.exists():
            with open(self.filepath, "r", encoding="utf-8") as f:
                data = json.load(f)
                self._todos = {
                    int(k): TodoItem(**v) for k, v in data.items()
                }

    def _save(self) -> None:
        """保存数据"""
        data = {str(k): v.__dict__ for k, v in self._todos.items()}
        with open(self.filepath, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

    def add(self, title: str, priority: str = "medium") -> TodoItem:
        """添加待办"""
        todo_id = max(self._todos.keys(), default=0) + 1
        todo = TodoItem(id=todo_id, title=title, priority=priority)
        self._todos[todo_id] = todo
        self._save()
        return todo

    def get(self, todo_id: int) -> TodoItem | None:
        """获取待办"""
        return self._todos.get(todo_id)

    def list(self, completed: bool | None = None) -> list[TodoItem]:
        """列出待办"""
        todos = list(self._todos.values())
        if completed is not None:
            todos = [t for t in todos if t.completed == completed]
        return sorted(todos, key=lambda t: (t.completed, t.created_at))

    def complete(self, todo_id: int) -> bool:
        """完成待办"""
        todo = self._todos.get(todo_id)
        if todo:
            todo.complete()
            self._save()
            return True
        return False

    def uncomplete(self, todo_id: int) -> bool:
        """取消完成"""
        todo = self._todos.get(todo_id)
        if todo:
            todo.uncomplete()
            self._save()
            return True
        return False

    def delete(self, todo_id: int) -> bool:
        """删除待办"""
        if todo_id in self._todos:
            del self._todos[todo_id]
            self._save()
            return True
        return False

12.4.4 命令行接口

# src/todo/cli.py
"""命令行接口"""
import sys
from typing import Literal

from .models import TodoItem
from .storage import Storage


def print_header() -> None:
    print("=" * 50)
    print("       待办事项管理器")
    print("=" * 50)


def print_todos(todos: list[TodoItem]) -> None:
    if not todos:
        print("暂无待办事项")
        return

    for todo in todos:
        print(f"  {todo.id:2d}. {todo}")

    print(f"\n共 {len(todos)} 项")


def add_todo(storage: Storage, title: str, priority: str) -> None:
    """添加待办"""
    todo = storage.add(title, priority)
    print(f"✓ 已添加: {todo.title}")


def list_todos(storage: Storage, filter_completed: bool | None) -> None:
    """列出待办"""
    todos = storage.list(filter_completed)
    print_todos(todos)


def complete_todo(storage: Storage, todo_id: int) -> None:
    """完成待办"""
    if storage.complete(todo_id):
        print(f"✓ 已完成待办 #{todo_id}")
    else:
        print(f"✗ 未找到待办 #{todo_id}")


def delete_todo(storage: Storage, todo_id: int) -> None:
    """删除待办"""
    if storage.delete(todo_id):
        print(f"✓ 已删除待办 #{todo_id}")
    else:
        print(f"✗ 未找到待办 #{todo_id}")


def main() -> None:
    """主函数"""
    storage = Storage()

    if len(sys.argv) < 2:
        print_header()
        print("用法: todo <命令> [参数]")
        print("命令:")
        print("  add <标题> [--priority low|medium|high]")
        print("  ls [--completed] [--pending]")
        print("  done <ID>")
        print("  rm <ID>")
        return

    command = sys.argv[1]

    match command:
        case "add":
            if len(sys.argv) < 3:
                print("请输入待办标题")
                return

            title = sys.argv[2]
            priority = "medium"
            if "--priority" in sys.argv:
                idx = sys.argv.index("--priority")
                if idx + 1 < len(sys.argv):
                    priority = sys.argv[idx + 1]

            add_todo(storage, title, priority)

        case "ls" | "list":
            completed: bool | None = None
            if "--completed" in sys.argv:
                completed = True
            elif "--pending" in sys.argv:
                completed = False

            list_todos(storage, completed)

        case "done" | "complete":
            if len(sys.argv) < 3:
                print("请输入待办 ID")
                return

            try:
                todo_id = int(sys.argv[2])
                complete_todo(storage, todo_id)
            except ValueError:
                print("无效的 ID")

        case "rm" | "delete":
            if len(sys.argv) < 3:
                print("请输入待办 ID")
                return

            try:
                todo_id = int(sys.argv[2])
                delete_todo(storage, todo_id)
            except ValueError:
                print("无效的 ID")

        case _:
            print(f"未知命令: {command}")


if __name__ == "__main__":
    main()

12.4.5 包初始化

# src/todo/__init__.py
"""Todo 应用包"""
from .models import TodoItem
from .storage import Storage

__version__ = "1.0.0"
__all__ = ["TodoItem", "Storage"]

12.4.6 测试用例

# tests/test_todo.py
"""测试用例"""
import pytest
from datetime import datetime

from todo.models import TodoItem
from todo.storage import Storage


@pytest.fixture
def storage(tmp_path):
    """创建临时存储"""
    filepath = tmp_path / "todos.json"
    return Storage(filepath=str(filepath))


def test_add_todo(storage):
    """测试添加待办"""
    todo = storage.add("学习 Python", "high")
    assert todo.id == 1
    assert todo.title == "学习 Python"
    assert todo.priority == "high"
    assert todo.completed is False


def test_list_todos(storage):
    """测试列出待办"""
    storage.add("任务1")
    storage.add("任务2")

    todos = storage.list()
    assert len(todos) == 2


def test_complete_todo(storage):
    """测试完成待办"""
    todo = storage.add("任务")
    assert todo.completed is False

    storage.complete(todo.id)

    updated = storage.get(todo.id)
    assert updated.completed is True
    assert updated.completed_at is not None


def test_delete_todo(storage):
    """测试删除待办"""
    todo = storage.add("任务")
    result = storage.delete(todo.id)

    assert result is True
    assert storage.get(todo.id) is None


def test_empty_list(storage):
    """测试空列表"""
    todos = storage.list()
    assert len(todos) == 0

12.4.7 pyproject.toml

[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "todo-app"
version = "1.0.0"
description = "命令行待办事项管理器"
requires-python = ">=3.10"
dependencies = []

[project.scripts]
todo = "todo.cli:main"

[tool.pytest.ini_options]
testpaths = ["tests"]

12.5 代码规范工具

12.5.1 Ruff(推荐)

# 安装
pip install ruff

# 检查代码
ruff check .

# 自动修复
ruff check . --fix

# 格式化代码
ruff format .

# 创建配置
ruff init

12.5.2 Black

# 安装
pip install black

# 格式化
black .

# 检查(不修改)
black --check .

# 自定义行长度
black --line-length 100 .

12.5.3 mypy

# 安装
pip install mypy

# 类型检查
mypy src/

# 严格模式
mypy src/ --strict

12.5.4 pytest

# 安装
pip install pytest pytest-cov

# 运行测试
pytest

# 带覆盖率
pytest --cov=src --cov-report=html

# 运行特定测试
pytest tests/test_todo.py::test_add_todo

12.6 Git 使用

12.6.1 基本命令

# 初始化仓库
git init

# 克隆仓库
git clone https://github.com/user/repo.git

# 添加文件
git add .
git add file.py

# 提交
git commit -m "feat: 添加新功能"

# 推送
git push origin main

# 拉取
git pull origin main

# 查看状态
git status

# 查看差异
git diff

12.6.2 .gitignore 示例

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# 虚拟环境
venv/
ENV/
env/
.venv

# IDE
.vscode/
.idea/
*.swp
*.swo

# 测试
.pytest_cache/
.coverage
htmlcov/
.tox/

# 环境变量
.env
.env.local

# 日志
*.log

# 数据文件
*.json
!example.json
todos.json

12.7 项目建议汇总

初学者项目(1-2周)

项目 难度 涉及内容
命令行计算器 函数、控制流
待办事项管理器 文件 I/O、dataclass、CLI
猜数字游戏 随机数、循环
文本统计工具 ⭐⭐ 文件处理、正则、字典
通讯录管理 ⭐⭐ CRUD、JSON、类

中级项目(2-4周)

项目 难度 涉及内容
天气查询 CLI ⭐⭐ HTTP 请求、API、JSON
RSS 阅读器 ⭐⭐ XML/JSON、网络、缓存
密码生成器 ⭐⭐ 随机数、加密、CLI
Markdown 转 HTML ⭐⭐ 正则、文件处理
简易爬虫 ⭐⭐⭐ HTTP、BeautifulSoup

高级项目(4周以上)

项目 难度 涉及内容
REST API 服务 ⭐⭐⭐ FastAPI、数据库、认证
图片批量处理 ⭐⭐⭐ Pillow、多线程
博客系统 ⭐⭐⭐ Web、数据库、模板
任务调度器 ⭐⭐⭐ 定时任务、通知

继续学习建议

深入 Python

  1. 官方文档https://docs.python.org/3/
  2. PEP 规范https://peps.python.org/
  3. Real Pythonhttps://realpython.com/

Web 开发

  1. Flask:轻量级 Web 框架
  2. Django:全功能 Web 框架
  3. FastAPI:现代异步 Web 框架

数据科学

  1. NumPy:数值计算
  2. Pandas:数据分析
  3. Matplotlib:数据可视化
  4. Scikit-learn:机器学习

自动化

  1. Selenium:Web 自动化
  2. Playwright:现代自动化
  3. Scrapy:网页爬虫

测试

  1. pytest:主流测试框架
  2. unittest:标准库测试
  3. Coverage.py:代码覆盖率

课后练习

练习 12.1:完善 Todo 应用

  1. 添加优先级排序功能
  2. 添加搜索功能
  3. 添加截止日期功能

练习 12.2:创建新项目

  1. 设计项目结构
  2. 编写功能代码
  3. 编写测试用例
  4. 配置代码规范工具

练习 12.3:参与开源

  1. 找到一个感兴趣的开源项目
  2. 阅读代码和文档
  3. 提交第一个 Pull Request

本章小结

本章我们详细学习了项目开发:

  1. 项目结构

    • 标准的目录布局
    • src 和 tests 分离
  2. 配置管理

    • pyproject.toml
    • requirements.txt
    • 虚拟环境
  3. 代码规范

    • Ruff、Black、mypy
    • pytest 测试
  4. 版本控制

    • Git 基本使用
    • .gitignore
  5. 实战项目

    • 完整的 Todo 应用
    • 项目建议

相关资源


评论