零基础入门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)
相关推荐
gopher95112 小时前
RUST:错误处理 thiserror 详解
开发语言·后端·rust
破茧蝴蝶10 小时前
Python分布式爬虫完整教程
开发语言·python
jack1868911 小时前
百度 AI 搜索实战:从精准获客到智能决策的应用全景
windows
benchmark_cc11 小时前
量化系统架构设计:为什么应该将数据获取与策略逻辑解耦?
python·数据分析·pandas·量化交易·股票数据·quantdash
狗凯之家源码网11 小时前
全新 PHP 轻量客服聊天室源码深度评测
开发语言·php·聊天室系统
凤山老林11 小时前
Spring Boot 启动冲刺:耗时剖析、懒加载策略与事件驱动初始化优化
spring boot·后端·python
小白快快跑哦11 小时前
python-字符串全解(六):正则表达式-逻辑运算或
python·正则表达式·字符串·逻辑运算
远离UE411 小时前
UE5 Substrate Thin Film 源码拆析
开发语言·javascript·ue5
keyipatience11 小时前
JavaScript学习(1)书写位置,变量、数据类型、运算符
开发语言·前端·javascript·css·学习
Thinker QAQ11 小时前
并发编程(九):BlockingQueue 与 Channel——语言层的原子性、可见性与有序性
java·python·go·并发编程·channel·blockingqueue·message passing