CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Python 语言基础/课时/综合练习与下一步
RawBlame
课时目录12 节

Python 语言基础

  1. 01Python 是什么
  2. 02语法与缩进
  3. 03类型与变量
  4. 04条件与循环
  5. 05函数
  6. 06列表字典与集合
  7. 07字符串处理
  8. 08文件读写
  9. 09模块与 pip
  10. 10类与对象入门
  11. 11异常处理
  12. 12综合练习与下一步

综合练习与下一步

小脚本、数据分析与 AI 路线指引。

45 分钟

综合练习与下一步

这一课你将能做到

  1. 独立完成一个多文件小项目「记账本」:数据类 + 文件持久化 + 异常兜底 + 交互循环
  2. 用十二课学到的每一个模块对应项目里的具体代码位置,形成「知识地图」
  3. 说清从 Python 基础出发的三条进阶路线(数据 / AI / 工程化)各自要补什么

为什么要学

零散知识不等于能力。前十一课你学了缩进、类型、循环、函数、容器、字符串、文件、模块、类、异常——但只有把它们装进同一个真实程序里,才能检验哪些真会了、哪些只是「看过」。这一课的项目刻意覆盖全部十二课的考点,做完它,Python 基础就算真正闭环;做完之后去哪,这一课也给你地图。

人话:前面是学招式,这一课是打一场完整的实战。招式背得再熟,实战里忘了出招顺序(先存数据还是先算统计)才算真掌握。

先建立图像

记账本(expense tracker)项目结构:

python-lab/
├── .venv/                  ← 第 9 课:虚拟环境
├── models.py               ← 第 10 课:数据类
├── storage.py              ← 第 8 课:JSON 文件持久化
├── stats.py                ← 第 6/7 课:容器统计与字符串格式化
├── main.py                 ← 第 2/4/11 课:交互循环 + 异常兜底
└── expenses.json           ← 数据文件(运行后自动生成)
程序数据流:

用户输入 → 解析校验(异常) → 记账对象(类) → 存盘(文件/JSON)
                                        ↘ 统计报表(字典统计 + f-string)

跟着做

步骤 1:搭好项目骨架(第 9 课)

cd python-lab
python -m venv .venv
.venv\Scripts\activate        # macOS/Linux: source .venv/bin/activate

新建空文件 models.py、storage.py、stats.py、main.py。本步验收:终端提示符前有 (.venv),四个文件就位。

步骤 2:写数据层 models.py(第 10 课:类)

"""记账记录的数据模型"""
from datetime import datetime


class Expense:
    def __init__(self, category: str, amount: float, note: str = ""):
        if amount <= 0:
            raise ValueError(f"金额必须为正数,收到 {amount}")   # 第 11 课
        self.category = category
        self.amount = amount
        self.note = note
        self.created_at = datetime.now().strftime("%Y-%m-%d %H:%M")

    def to_dict(self) -> dict:
        return {
            "category": self.category,
            "amount": self.amount,
            "note": self.note,
            "created_at": self.created_at,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "Expense":
        """从字典重建对象(读文件时用)"""
        expense = cls(data["category"], data["amount"], data.get("note", ""))
        expense.created_at = data["created_at"]
        return expense

    def __str__(self) -> str:
        return f"{self.created_at}  {self.category:<6} {self.amount:>8.2f} 元  {self.note}"

要点回顾:__init__ 校验非法数据直接 raise;to_dict / 负责「对象 ↔ 字典」互转(JSON 只认字典); 让 print 出人话; 是不依赖具体实例的构造入口——这是你见到的第一个装饰器,会用即可。

from_dict
__str__
@classmethod

步骤 3:写持久化层 storage.py(第 8 课:文件 + JSON)

"""JSON 文件持久化"""
import json
from pathlib import Path
from models import Expense

DB_PATH = Path(__file__).parent / "expenses.json"   # 永远定位到脚本目录


def load_all() -> list[Expense]:
    if not DB_PATH.exists():
        return []
    try:
        with open(DB_PATH, encoding="utf-8") as f:
            raw = json.load(f)
        return [Expense.from_dict(item) for item in raw]    # 第 6 课:推导式
    except json.JSONDecodeError:
        print("警告:数据文件损坏,已按空账本启动")
        return []


def save_all(expenses: list[Expense]) -> None:
    with open(DB_PATH, "w", encoding="utf-8") as f:
        json.dump([e.to_dict() for e in expenses], f, ensure_ascii=False, indent=2)

要点回顾:with open + 显式 encoding="utf-8";ensure_ascii=False 保中文;文件损坏不崩程序而是降级处理;list[Expense] 这种类型注解 3.9+ 可用,属可读性增强。

步骤 4:写统计层 stats.py(第 6/7 课:容器与字符串)

"""统计报表"""
from collections import defaultdict
from models import Expense


def total(expenses: list[Expense]) -> float:
    return sum(e.amount for e in expenses)          # 生成器表达式


def by_category(expenses: list[Expense]) -> dict[str, float]:
    result: dict[str, float] = defaultdict(float)
    for e in expenses:
        result[e.category] += e.amount
    return dict(result)


def report(expenses: list[Expense]) -> str:
    if not expenses:
        return "账本是空的,先记一笔吧"
    lines = [f"共 {len(expenses)} 笔,合计 {total(expenses):.2f} 元", "-" * 36]
    for cat, amount in sorted(by_category(expenses).items(), key=lambda kv: -kv[1]):
        percent = amount / total(expenses)
        bar = "█" * round(percent * 20)             # 文本进度条
        lines.append(f"{cat:<8}{amount:>9.2f} 元 {percent:6.1%} {bar}")
    return "\n".join(lines)                          # 第 7 课:join

输出预览(记了三笔后运行报表):

共 3 笔,合计 58.50 元
------------------------------------
餐饮        45.50 元  77.8% ███████████████
交通         8.00 元  13.7% ██
娱乐         5.00 元   8.5% █

要点回顾:defaultdict 免去 get 初始化;sorted 的 key 用 lambda 按金额倒序;f-string 的对齐、百分比格式一次用全。

步骤 5:写主程序 main.py(第 2/4/11 课:循环 + 异常)

"""记账本主程序:交互循环"""
from models import Expense
from storage import load_all, save_all
from stats import report, by_category

MENU = """--- 记账本 ---
1. 记一笔
2. 看全部
3. 分类统计
4. 退出"""


def read_positive_float(prompt: str) -> float:
    """带重试的数字输入(第 11 课的落地)"""
    while True:
        raw = input(prompt).strip()
        try:
            value = float(raw)
            if value <= 0:
                print("金额要大于 0")
                continue
            return value
        except ValueError:
            print(f"“{raw}”不是数字,请重输")


def main():
    expenses = load_all()
    while True:                                        # while True + break 惯用法
        print(MENU)
        choice = input("选择操作:").strip()
        if choice == "1":
            category = input("分类(餐饮/交通/娱乐…):").strip() or "其他"
            amount = read_positive_float("金额:")
            note = input("备注(可回车跳过):").strip()
            expenses.append(Expense(category, amount, note))
            save_all(expenses)                          # 每记一笔立即落盘
            print("已记账 ✓")
        elif choice == "2":
            for i, e in enumerate(expenses, start=1):   # 第 4 课:enumerate
                print(f"{i}. {e}")
        elif choice == "3":
            print(report(expenses))
        elif choice == "4":
            print("再见,记得看报表")
            break
        else:
            print("没有这个选项,重选")


if __name__ == "__main__":                              # 第 9 课:双模式开关
    main()

运行 python main.py,按菜单走一遍:记两三笔 → 看全部 → 看统计 → 退出。重新运行程序,数据还在(storage 落盘生效)。

完整交互示例:

--- 记账本 ---
1. 记一笔
2. 看全部
3. 分类统计
4. 退出
选择操作:1
分类(餐饮/交通/娱乐…):餐饮
金额:abc
“abc”不是数字,请重输
金额:45.5
备注(可回车跳过):午饭
已记账 ✓

步骤 6:给自己出三道升级题(检验闭环)

在能跑通的基础上动手改,改得动才说明真懂:

  1. 加「删除某笔」功能:main 里加选项 5,按序号 expenses.pop(i - 1),注意序号越界要 try/except 或先判断。
  2. 按月份过滤报表:给 stats.report 加参数 month="2026-10",用 e.created_at.startswith(month) 过滤(第 7 课 startswith)。
  3. 支出上限告警:类属性 MONTHLY_LIMIT = 2000,统计时若 total(expenses) > MONTHLY_LIMIT 打印红色告警文本——想想该放类属性还是实例属性(第 10 课)。

每道题都对应明确课程:改完这三题,类型、容器、字符串、文件、类、异常、循环全部二次落地。

讲清楚要点

  1. 分层是项目感的起点:models(是什么)→ storage(存哪里)→ stats(算什么)→ main(怎么交互)。每层只管一件事,改统计不用碰存储——这就是第 5 课「函数只做一件事」在项目尺度的重演。
  2. 数据在层间「变形」:界面上是字符串 → 校验后是 float → 业务里是 Expense 对象 → 存盘时是 dict/JSON → 读回来再变回对象。每一跳用什么函数(float()、to_dict、json.dump)要能脱口而出。
  3. 「每记一笔就落盘」是简单可靠的策略:练习阶段不值得引入数据库,JSON 文件 + 每次全量重写足够;能说清它在大数据量下的局限(全量重写慢、无并发锁)就具备了谈「何时该换 SQLite/数据库」的判断力。
  4. 异常策略在项目里的分工:模型层 raise(拦截非法数据)、输入层 try/except + 重试(兜住用户手滑)、存储层捕获降级(文件坏了不崩程序)。三层三种姿态,正好是第 11 课「何时捕获、何时让它崩」的答案。
  5. 对象 ↔ 字典互转是 Python 工程的高频动作:API 响应、数据库行、JSON 文件全是字典,业务代码里是对象。写熟 to_dict/from_dict,将来见到 dataclass、pydantic 就秒懂——它们只是把这个样板代码自动化了。

你可能会懵的地方

  • 这个项目为什么不用数据库? 基础收官项目的目标是「巩固语言本身」,文件 + JSON 恰好覆盖第 8 课全部考点且零依赖。数据库(SQLite 一行 import 就能用)是下一步进阶的练习对象,放进路线图而不是塞进这一课。

  • from_dict 里 data["amount"] 不怕 KeyError 吗? 数据文件是本程序自己写入的,字段必然存在——内部约定可以信任(信任内部数据是基本工程判断)。若是外部来源(用户上传、第三方接口),就该逐字段校验或捕获 KeyError。区分「内部可信数据」和「外部不可信输入」是异常策略的核心。

  • @classmethod 和普通方法差在哪?什么时候用? 普通方法第一个参数是实例(self);类方法第一个参数是类(cls),不依赖任何实例。Expense.from_dict(...) 在还没有对象时就要造对象,只能类方法。见过的用法还有 dict.fromkeys()。

  • 想给它加 Web 界面,该学什么? 先学 HTTP 基础 + requests 调 API(把 CLI 的统计结果用 JSON 吐出去),再选 Flask/FastAPI 起 Web 服务。那是 python 路线图下一站的内容,本项目正好可以当数据源改造。

  • 代码里 list[Expense]、dict[str, float] 这些是什么? 类型注解:3.9+ 内置容器支持直接下标写法,运行时不强制,但 IDE 补全、mypy 静态检查都靠它。基础阶段建议养成「函数签名写注解」的习惯,函数体内不必过度标注。

错法对照

错法问题正解
所有代码塞一个 main.py三百行后没法维护按职责拆模块(models/storage/stats/main)
统计时重复调 total()每次全量重算,浪费且易不一致先算一次存变量再复用
退出前才统一存盘程序崩溃丢全部数据关键操作后立即落盘
输入金额不校验直接 float()用户手滑整个程序崩try/except + 重试循环
删除按 pop(i) 不处理越界IndexError 崩溃先判断 0 <= i < len 或捕获异常
报表函数里 print 而不是 return结果没法被测试和复用返回字符串,print 留给 main

卡点急救

  1. ModuleNotFoundError: models:没在项目目录运行,或 venv 没激活。cd python-lab 后再 python main.py。
  2. expenses.json 里中文是 \uXXXX:json.dump 漏了 ensure_ascii=False,补上重存。
  3. 报表金额加总对不上:检查是否混入了字符串金额(读回 JSON 后没转 float),在 from_dict 里显式 float(data["amount"])。
  4. while 循环退不出去:choice 分支里的 break 缩进错进了 else。确认 break 与 if/elif 同层。
  5. 改了 stats.py 不生效:Python 模块缓存——确认保存了文件、运行的是当前解释器(venv 激活状态);IDE 里可能跑的是旧缓存,重开终端验证。

面试常问

  • 给你一个需求(如记账/待办清单),你如何设计程序结构? 答题框架:分层(模型/存储/逻辑/入口)→ 数据流(输入校验 → 业务对象 → 持久化 → 统计输出)→ 异常策略(哪里 raise、哪里 catch)→ 可扩展点(换存储、加导出)。能对上本课项目每一层的设计理由即可。

  • dict 和对象各适合承载业务数据吗?怎么选? dict 适合临时、动态、跨边界传输(JSON、API);对象适合字段固定、带行为和校验的业务实体。工程上常见「边界用 dict、核心用对象」,用 to_dict/from_dict 显式转换,或用 dataclass/pydantic 减少样板。

  • 如何保证用户输入不会让程序崩溃? 三层防线:输入层 try/except 转换失败并重试;校验层 raise 值错误并给出可读信息;主循环兜底捕获未知异常并记录日志。核心是「外部输入永远不可信」。

  • 用 JSON 文件做持久化有什么局限?什么时候升级方案? 局限:全量读写、无并发控制、无索引查询、数据量大内存压力大。升级信号:数据量上万条、多进程/多用户写、需要按条件查询——此时换 SQLite(标准库 sqlite3,零部署)。

  • 这个基础课程之后,Python 学习路线怎么走? 按目标分流:数据处理 → pandas/numpy;Web/API → requests、FastAPI;AI 应用 → 大模型 SDK、prompt 工程;工程质量 → 类型注解 + mypy、pytest、logging。共同前提是先把标准库用熟(本课 1-11 节)。

自测三问

  1. 用户输入 45.5 到它在 JSON 文件里被存下,中间经历了哪几次「形态转换」?各用什么函数?
  2. models / storage / stats / main 四个模块各自的单一职责是什么?为什么统计函数返回字符串而不是直接 print?
  3. 升级题 2(按月过滤)你打算改哪几个文件的哪几行?用到了哪些课的知识点?

小结一句话

十二课的语法点在「记账本」里各就各位:类管数据、文件管持久化、容器管统计、异常管输入、循环管交互——能独立改出三个升级功能,Python 基础就真正闭环了。

延伸

  • The Python Tutorial(通读一遍查漏补缺)
  • Python 官方标准库索引
  • 站内:/roadmaps/python 继续下一阶段
  • 下一步预告:三条进阶路线任选其一开启——数据处理(pandas)、Web/API(requests + FastAPI)、AI 应用(大模型 SDK);无论哪条,先把这个项目的三个升级题做完。
加载本节练习…
← 上一课异常处理