系列 · Python 零基础到简单脚本 第 9 篇 / 共 11 篇 教程

Python 完整订单脚本图解:从 JSON 输入到汇总输出

把文件、JSON、容器、判断、循环、函数和错误处理串成一份可运行订单脚本,从三条输入记录得到 2 单、328.0 的已支付汇总。

作者:黄撑 更新于 2026-08-27
本文目录 8 节 · 点击展开

前八章分别拆开了变量、容器、条件、循环、函数、文件和错误路径。这一章不再增加新语法,而是把它们装进一份可以独立运行的订单脚本:从 orders.json 读取三条记录,校验并规范字段,筛选已支付订单,统计数量和金额,最后写出 paid_orders_summary.json

固定输入中,A001 和 A003 已支付,因此脚本的基准结果必须是:2 单,总金额 328.0。

先看整条数据管道

脚本只做五件事,每一步都有明确的输入和输出:

完整任务总览外部文件经过读取、规范、筛选和统计,最终变成新的结果文件
  1. 01 · 读取orders.jsonjson.load → list[dict]
  2. 02 · 校验 / 规范字段齐全、类型可用去空格、SKU 大写、金额转 float
  3. 03 · 筛选status == “已支付”A001、A003 进入统计
  4. 04 · 汇总数量 2 · 金额 328.0同时保留问题记录
  5. 05 · 写出paid_orders_summary.jsonUTF-8 中文 JSON

这条管道刻意保持单一:一个输入文件、一个脚本、一个输出文件。它不需要配置层、数据库或第三方数据框架。

准备固定输入文件

在脚本所在目录创建 orders.json

[
  {"order_no": "A001", "sku": "K161", "amount": 129.0, "status": "已支付"},
  {"order_no": "A002", "sku": "K162", "amount": 88.0, "status": "已退款"},
  {"order_no": "A003", "sku": "K161", "amount": 199.0, "status": "已支付"}
]

四个字段各有职责:

字段规范后的形态在脚本中的用途
order_no去掉两端空格的字符串标识订单、写入结果
sku去空格并转大写的字符串保留统一商品编码
amountfloat参与金额统计
status去掉两端空格的字符串决定是否进入已支付统计

完整脚本:复制后可以直接运行

把下面代码保存为 process_orders.py,与 orders.json 放在同一目录:

import json
from pathlib import Path


# A · 输入与输出位置
INPUT_PATH = Path("orders.json")
OUTPUT_PATH = Path("paid_orders_summary.json")


# B · 从 JSON 文件取得一批原始订单
def load_orders(path):
    with path.open("r", encoding="utf-8") as file:
        orders = json.load(file)

    if not isinstance(orders, list):
        raise TypeError("orders.json 顶层必须是列表")

    return orders


# C · 校验并规范一条订单
def normalize_order(raw_order):
    if not isinstance(raw_order, dict):
        raise TypeError("每条订单必须是字典")

    required_fields = {"order_no", "sku", "amount", "status"}
    missing_fields = required_fields - raw_order.keys()

    if missing_fields:
        missing_text = ", ".join(sorted(missing_fields))
        raise KeyError(f"缺少字段:{missing_text}")

    return {
        "order_no": str(raw_order["order_no"]).strip(),
        "sku": str(raw_order["sku"]).strip().upper(),
        "amount": float(raw_order["amount"]),
        "status": str(raw_order["status"]).strip(),
    }


# D · 筛选已支付订单并生成汇总
def build_summary(clean_orders, problems):
    paid_order_nos = []
    paid_count = 0
    paid_amount = 0.0

    for order in clean_orders:
        if order["status"] != "已支付":
            continue

        paid_order_nos.append(order["order_no"])
        paid_count = paid_count + 1
        paid_amount = paid_amount + order["amount"]

    return {
        "paid_order_nos": paid_order_nos,
        "paid_count": paid_count,
        "paid_amount": round(paid_amount, 2),
        "problem_count": len(problems),
        "problems": problems,
    }


# E · 写出结果,并把前面四段串起来
def write_summary(path, summary):
    with path.open("w", encoding="utf-8") as file:
        json.dump(summary, file, ensure_ascii=False, indent=2)


def main():
    raw_orders = load_orders(INPUT_PATH)
    clean_orders = []
    problems = []

    for raw_order in raw_orders:
        try:
            clean_order = normalize_order(raw_order)
        except (KeyError, TypeError, ValueError) as error:
            order_no = (
                raw_order.get("order_no", "未知订单")
                if isinstance(raw_order, dict)
                else "未知订单"
            )
            problems.append({
                "order_no": order_no,
                "error_type": type(error).__name__,
                "message": str(error),
            })
            continue

        clean_orders.append(clean_order)

    summary = build_summary(clean_orders, problems)
    write_summary(OUTPUT_PATH, summary)

    print("已写出:", OUTPUT_PATH)
    print("已支付订单:", summary["paid_count"])
    print("已支付金额:", summary["paid_amount"])
    print("问题记录:", summary["problem_count"])


if __name__ == "__main__":
    main()

在该目录运行:

python process_orders.py

如果环境使用 python3py,替换命令里的 python 即可。正常输出:

已写出: paid_orders_summary.json
已支付订单: 2
已支付金额: 328.0
问题记录: 0

五段代码分别负责什么

完整代码不应被看成一整堵符号。注释 A~E 把它分成五个职责,每段只回答一个问题:

完整代码分段地图每段都有清晰输入、动作和交给下一段的结果
  1. A · 路径INPUT_PATH / OUTPUT_PATH

    输入 两个文件名

    交付 可复用的 Path 对象

  2. B · 读取load_orders(path)

    输入 orders.json

    交付 raw_orders 列表

  3. C · 规范normalize_order(raw_order)

    输入 一条原始记录

    交付 字段可用的订单,或具体异常

  4. D · 统计build_summary(…)

    输入 clean_orders + problems

    交付 汇总字典

  5. E · 编排 / 写出main() / write_summary()

    输入 前四段的结果

    交付 JSON 文件和终端摘要

阅读顺序先看 main() 怎样调用各段,再进入某个函数看细节

main() 是整条管道的装配点。它没有把所有逻辑重新写一遍,只负责按顺序传递结果:raw_orders → clean_orders / problems → summary → 输出文件

三条订单逐条经过脚本

clean_ordersproblems 一开始都是空列表。每条记录先经过 normalize_order();规范成功后才有资格参与状态筛选。

三条订单状态台账每一轮都显示当前记录、规范结果、筛选结果和累计状态
第 1 轮A001 · K161amount 129.0 · 已支付
校验 / 规范通过金额保持 129.0
状态筛选命中已支付A001 进入已支付统计
数量变化0 → 1
金额变化0.0 → 129.0
第 2 轮A002 · K162amount 88.0 · 已退款
校验 / 规范通过金额保持 88.0
状态筛选未命中A002 不进入已支付统计
数量变化1 → 1
金额变化129.0 → 129.0
第 3 轮A003 · K161amount 199.0 · 已支付
校验 / 规范通过金额保持 199.0
状态筛选命中已支付A003 进入已支付统计
数量变化1 → 2
金额变化129.0 → 328.0
已支付订单A001 · A003最终数量2最终金额328.0问题记录0

注意“校验成功”和“业务状态命中”是两回事。A002 的字段完全合法,所以它进入 clean_orders;只是状态为“已退款”,因此没有进入已支付统计。

输入文件与输出文件怎样对应

脚本不会把完整输入原样复制到结果文件。输入面向“逐条处理”,输出面向“继续使用汇总”:

文件前后对照三条明细记录经过规则,收敛成一份可交付摘要
输入 · orders.json
A001K161 · 129.0 · 已支付
A002K162 · 88.0 · 已退款
A003K161 · 199.0 · 已支付
3 条订单明细
筛选 + 汇总A002 不进入已支付统计
输出 · paid_orders_summary.json
paid_order_nos[“A001”, “A003”]
paid_count2
paid_amount328.0
problem_count0
1 份业务摘要

实际写出的 JSON 是:

{
  "paid_order_nos": [
    "A001",
    "A003"
  ],
  "paid_count": 2,
  "paid_amount": 328.0,
  "problem_count": 0,
  "problems": []
}

这个文件可以继续交给报表、通知或其他脚本;本章不再增加这些下游步骤。

补充路径:一条金额异常怎样离开主线

基准输入没有坏数据。现在只做一次补充实验:把 A003 的金额从 199.0 改成字符串 "未知",其他字段不变。

[
  {"order_no": "A001", "sku": "K161", "amount": 129.0, "status": "已支付"},
  {"order_no": "A002", "sku": "K162", "amount": 88.0, "status": "已退款"},
  {"order_no": "A003", "sku": "K161", "amount": "未知", "status": "已支付"}
]

同一份 process_orders.py 不需要修改。A003 到达 float(raw_order["amount"]) 时产生 ValueError,被 main() 里针对单条记录的处理器接住,写入 problems,然后继续结束本轮。

坏金额补充路径A003 在规范阶段退出,不会带着无效金额进入筛选和累计
  1. 输入 · 01A003amount = “未知”
  2. 出错行 · 02转换金额float(“未知”)产生 ValueError
  3. 错误分支 · 03记录问题problems += A003error_type = ValueError
  4. 停止本条 · 04不进入业务统计continue没有无效 amount 参与求和
仍可用A001、A002问题记录A003 · ValueError补充案例汇总已支付 1 单 · 129.0

这次输出文件中的核心字段会类似下面(具体错误文字可能随 Python 版本略有不同):

{
  "paid_order_nos": ["A001"],
  "paid_count": 1,
  "paid_amount": 129.0,
  "problem_count": 1,
  "problems": [
    {
      "order_no": "A003",
      "error_type": "ValueError",
      "message": "could not convert string to float: '未知'"
    }
  ]
}

错误路径的关键不是“程序没有停”,而是结果仍然诚实:A003 没有被算进金额,同时订单号、异常类型和原因被明确留下。把 orders.json 恢复成基准数据后再次运行,结果就会回到 2 单、328.0。

用结果反查每个阶段

运行完成后,不要只看“有没有生成文件”。按下面顺序验证:

  1. paid_order_nos 是否只包含 A001、A003。
  2. paid_count 是否等于列表长度 2
  3. paid_amount 是否等于 129.0 + 199.0 = 328.0
  4. 基准数据的 problem_count 是否为 0
  5. 把 A003 金额改坏后,它是否出现在 problems,并从统计中消失。