零基础入门python07:让程序记住数据——JSON文件和异常处理

零基础入门python07:让程序记住数据------JSON文件和异常处理

一、上一篇课后练习讲解

结算器练习可以使用普通 assert:断言原价、折扣价和负配送费异常。断言表达"程序员确信必须成立的规则",而用户输入错误应该用 try/except 给出友好提示。

可执行验收答案

把测试保存为 test_bill.py,直接用 Python 运行即可:

python 复制代码
from decimal import Decimal
from bill_calculator import calculate_order

assert calculate_order(Decimal("50"), 2, Decimal("5"), "normal")["total"] == Decimal("105")

"应该抛异常"的调用不能直接放在 assert 后面。正确写法是:

python 复制代码
try:
    calculate_order(Decimal("-1"), 1, Decimal("0"), "normal")
except ValueError as exc:
    assert "价格" in str(exc)
else:
    raise AssertionError("负价格没有被拒绝")
print("bill validation passed")

运行 python test_bill.py 应输出 bill validation passed;若把 except 写成裸 except,会把编程错误也吞掉,排查会更困难。

二、本篇要完成什么

把个人账目保存到 records.json,下次启动还能读取;文件损坏或金额输入错误时,程序不崩溃,并说明问题。

三、完整实现

python 复制代码
from pathlib import Path
import json

DATA_FILE = Path('records.json')


def load_records() -> list[dict]:
    """读取持久化数据;文件不存在返回空列表,格式损坏则明确报错。"""
    if not DATA_FILE.exists():
        return []
    try:
        return json.loads(DATA_FILE.read_text(encoding='utf-8'))
    except json.JSONDecodeError as error:
        raise ValueError(f'账本文件不是合法JSON:{error}') from error


def save_records(records: list[dict]) -> None:
    """统一使用UTF-8和缩进保存,便于人工查看和版本控制。"""
    DATA_FILE.write_text(json.dumps(records, ensure_ascii=False, indent=2), encoding='utf-8')


def add_record(records: list[dict], amount: float, note: str) -> None:
    if amount <= 0:
        raise ValueError('金额必须大于0')
    records.append({'amount': amount, 'note': note.strip()})


try:
    records = load_records()
    amount = float(input('本次支出金额:'))
    add_record(records, amount, input('备注:'))
    save_records(records)
    print(f'已保存,共{len(records)}条记录')
except (ValueError, OSError) as error:
    print(f'操作失败:{error}')

Path 负责路径,json 负责格式,业务函数负责金额规则。异常捕获只包住可能失败的操作,不用一个巨大的 except Exception 把真正的编程错误隐藏掉。

四、运行结果和验收

第一次运行会创建 records.json;再次运行会在原列表后追加记录。手工把文件改成 {bad,程序应输出"不是合法JSON",而不是打印 traceback。课后练习:增加收入/支出 kind 字段,并在下一篇用类封装账本对象。

实战补充:JSON 记账本的读写边界

文件项目的核心不是 json.loads,而是定义文件不存在、内容损坏和保存失败时的行为。数据层负责读写,命令行层负责提示。

python 复制代码
def load_records(path: Path) -> list[dict]:
    if not path.exists():
        return []
    try:
        data = json.loads(path.read_text(encoding='utf-8'))
    except json.JSONDecodeError as exc:
        raise ValueError('账本文件不是有效 JSON') from exc
    if not isinstance(data, list):
        raise ValueError('账本根节点必须是列表')
    return data

def save_records(path: Path, records: list[dict]) -> None:
    temp = path.with_suffix('.tmp')
    temp.write_text(json.dumps(records, ensure_ascii=False, indent=2), encoding='utf-8')
    temp.replace(path)

先写临时文件再替换,避免程序写到一半被终止而留下半个 JSON。金额读取后转换 Decimal,保存时再转成字符串,避免浮点污染。

验收与课后练习

首次运行创建空账本,写入两条记录,故意破坏 JSON 后得到可读错误。课后增加导出 CSV,下一篇用类封装账本生命周期。

本篇结束:完整模块文件

本节不是代码片段,而是本篇结束时该模块的完整版本。请先备份旧文件,再整体替换;替换后重新运行本篇命令和测试。阅读时重点看本篇新增的函数、事务边界和错误处理,未涉及的代码先不要自行删减。

main.py

python 复制代码
from pathlib import Path
import json
path=Path('records.json')
try: records=json.loads(path.read_text('utf-8')) if path.exists() else []
except json.JSONDecodeError: records=[]
try:
    amount=float(input('支出金额:'))
    if amount<=0: raise ValueError('金额必须大于0')
    records.append({'amount':amount,'note':input('备注:')})
    path.write_text(json.dumps(records,ensure_ascii=False,indent=2),'utf-8')
except ValueError as error: print('输入错误:',error)
相关推荐
清水白石0081 小时前
Python 如何设计一个线程安全的缓存?从锁策略、LRU 到工程化实战
python·安全·缓存
qq_22589174661 小时前
基于Flask的城市地铁客流量数据预测系统设计与实现
后端·python·flask
W_326001 小时前
Python 常用标准 / 第三方库:random、tqdm、turtle、jieba 用法
开发语言·python
OptimizationMaster1 小时前
Python对视频文件分类,“横屏”和“竖屏”
python·视频
XLYcmy1 小时前
PDF论文处理器 - 功能总结
数据库·python·pdf·csv·pymupdf·dify·文本分割
Uncommon.1 小时前
线性代数与向量
pytorch·python·线性代数·机器学习
ZC跨境爬虫2 小时前
LeetCode 88. 合并两个有序数组(双指针详解 + Java Python 实现)
java·python·算法·leetcode
caimouse2 小时前
ReactOS 图形系统分析(17):区域填充 — EngPaint / IntEngPaint(paint.c)
c语言·开发语言·算法