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

Python 文件与 JSON 图解:外部数据怎样进入代码

用三条订单看懂 JSON 文件如何解析成 Python 列表与字典、Python 对象如何写回文件,以及 load、loads、dump、dumps 的输入输出区别。

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

前面的订单大多直接写在代码里。真实脚本里的数据却常常先存在硬盘文件、接口响应或配置文本中。Python 不能隔着文件直接操作订单字段,必须先跨过一道边界:把外部文本读取并解析成列表、字典等 Python 对象。

处理完成后,方向反过来:Python 对象先变成 JSON 文本,再写回硬盘。这章要看清的就是这两个方向,以及每一步的数据形态。

orders.json 还在 Python 外部

先在脚本旁边创建 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": "已支付"}
]

文件看起来很像 Python 的列表和字典,但此时它仍然只是硬盘上的 UTF-8 文本。下面的代码才会打开文件并解析:

import json

with open("orders.json", "r", encoding="utf-8") as file:
    orders = json.load(file)

print(type(orders))
print(type(orders[0]))
print(orders[0]["status"])

输出:

<class 'list'>
<class 'dict'>
已支付
读取与解析轨道orders.json 文本经过 json.load() 后,才成为可访问字段的 list[dict]
  1. 外部输入 · 01硬盘文件orders.json形态:UTF-8 JSON 文本
  2. 打开 · 02取得文件对象open(…, “r”)方向:读取
  3. 当前操作 · 03读取并解析 JSONjson.load(file)文本语法转换为 Python 类型
  4. 之后 · 04内存中的对象orders · list[dict]可以读取 orders[0][“status”]
之前 文件外部的字符边界 json.load(file)之后 3 条 Python 订单字典

with 会在代码离开缩进区域时关闭文件;不需要自己记住何时调用 close()encoding="utf-8" 明确告诉 Python 怎样解释中文字符,避免依赖不同电脑的默认编码。

打开文件时,只先记住三个模式

open() 的第二个参数决定方向:

模式方向文件已存在时适合场景
"r"读取保留原内容读取订单、配置和文本
"w"写入清空后重写生成一份完整结果文件
"a"追加保留原内容,在末尾增加追加简单记录

读取普通文本时,file.read() 返回字符串:

with open("order_no.txt", "r", encoding="utf-8") as file:
    content = file.read()

print(type(content))

JSON 文件的区别是多一步“按 JSON 语法解析”。因此不要混淆:

  • file.read():得到原始字符串。
  • json.load(file):从文件对象读取 JSON,并得到 Python 对象。

写入时尤其要确认 "w" 会覆盖原内容。如果目标文件需要保留历史,先判断是否应该换文件名、备份,或明确使用追加模式。

Python 对象怎样写回 JSON 文件

假设前面的处理已经得到固定结果:已支付订单是 A001、A003,共 2 单,总金额 328.0。先用 Python 字典保存汇总:

summary = {
    "paid_order_nos": ["A001", "A003"],
    "paid_count": 2,
    "paid_amount": 328.0,
}

接着使用 json.dump() 写入 paid_orders_summary.json

import json

summary = {
    "paid_order_nos": ["A001", "A003"],
    "paid_count": 2,
    "paid_amount": 328.0,
}

with open("paid_orders_summary.json", "w", encoding="utf-8") as file:
    json.dump(summary, file, ensure_ascii=False, indent=2)
序列化与写回轨道Python 字典经过 json.dump(),成为硬盘上的 JSON 文件
  1. 内部输入 · 01Python 对象summary · dict含列表、整数和浮点数
  2. 打开 · 02取得目标文件对象open(…, “w”)方向:覆盖写入
  3. 当前操作 · 03序列化并写入json.dump(summary, file)ensure_ascii=False 保留中文
  4. 外部输出 · 04结果文件paid_orders_summary.json可以交给下一步程序
之前 内存中的 dict边界 json.dump(data, file)之后 硬盘上的 JSON 文本

生成的文件内容是:

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

ensure_ascii=False 让中文直接写入;indent=2 只负责排版,让文件更易读,不改变数据含义。

load / loads / dump / dumps 按输入形态区分

四个名字只差一个 s,死记很容易混。先判断数据现在是在文件对象里,还是已经是一段字符串;再判断方向是进入 Python,还是离开 Python。

JSON 四入口映射load 系列进入 Python,dump 系列离开 Python;末尾 s 表示字符串端点
解析方向JSON → Python 对象
json.load(file)

输入 JSON 文件对象

输出 list / dict 等对象

文件 → Python
json.loads(text)

输入 JSON 字符串

输出 list / dict 等对象

string → Python
序列化方向Python 对象 → JSON
json.dump(data, file)

输入 Python 对象 + 文件对象

结果 JSON 写入文件

Python → 文件
json.dumps(data)

输入 Python 对象

输出 JSON 字符串

Python → string

四种形式的最小代码如下:

import json

# 文件 → Python 对象
with open("orders.json", "r", encoding="utf-8") as file:
    from_file = json.load(file)

# JSON 字符串 → Python 对象
text = '{"order_no": "A001", "status": "已支付"}'
from_text = json.loads(text)

# Python 对象 → JSON 字符串
json_text = json.dumps(from_text, ensure_ascii=False)

# Python 对象 → JSON 文件
with open("one_order.json", "w", encoding="utf-8") as file:
    json.dump(from_text, file, ensure_ascii=False, indent=2)

记忆时不要只念函数名,而要把完整方向一起说出来:loads 是“JSON 字符串进入 Python”,dumps 是“Python 对象变成 JSON 字符串”。

文件和 API 最终可以汇入同一处理函数

文件与 API 的来源不同,但只要进入业务逻辑前都得到同样的 list[dict],后面的函数就不需要知道数据来自哪里。

文件路线由 json.load(file) 得到对象。很多 API 客户端则提供类似 response.json() 的动作,直接给出已经解析好的列表或字典;如果接口工具只给原始 JSON 字符串,才需要先调用 json.loads()

两种来源的汇合点来源不同,进入处理函数前的数据形状相同
来源 A · 文件orders.jsonjson.load(file)list[dict]
来源 B · APIresponseAPI 客户端已解析list[dict]
共同输入契约orders:每一项都有 order_no、sku、amount、status
summarize_paid_orders(orders)paid_count = 2 · paid_amount = 328.0

下面的处理函数可以直接接收任一路线得到的 orders

import json

def summarize_paid_orders(orders):
    paid_orders = [
        order for order in orders
        if order["status"] == "已支付"
    ]
    return {
        "paid_order_nos": [order["order_no"] for order in paid_orders],
        "paid_count": len(paid_orders),
        "paid_amount": sum(order["amount"] for order in paid_orders),
    }

with open("orders.json", "r", encoding="utf-8") as file:
    file_orders = json.load(file)

# 模拟 API 客户端已经解析完成的返回值
api_orders = [
    {"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": "已支付"},
]

print(summarize_paid_orders(file_orders))
print(summarize_paid_orders(api_orders))

两次结果相同。真正实现网络请求时可以更换 API 工具,但 summarize_paid_orders() 不需要跟着改;它只依赖输入形状,不依赖来源。

pathlib 让路径意图更清楚

相对路径取决于脚本运行时的当前目录,不一定等于脚本文件所在目录。先用 Path.cwd() 看当前目录:

from pathlib import Path

print(Path.cwd())

构造 data/orders.json 时,可以让目录和文件名分开表达:

import json
from pathlib import Path

file_path = Path("data") / "orders.json"

with file_path.open("r", encoding="utf-8") as file:
    orders = json.load(file)

print(len(orders))

pathlib 属于 Python 标准库,不需要额外安装。入门阶段先掌握 Path.cwd()Path(...) / "name"Path.open() 就足够了。

文件边界可能失败,但处理方式留到下一篇

这条数据管道只有输入满足边界条件时才能继续:

错误边界文件必须存在、能按 UTF-8 读取,并且内容符合 JSON 语法

失败可能发生在“打开”“解码”或“JSON 解析”步骤。先根据报错找到具体停止点,不要用默认值悄悄掩盖问题。

本章不展开 try / except、异常匹配或跳过坏记录。下一篇会把 FileNotFoundErrorUnicodeDecodeErrorJSONDecodeError 放回各自发生的步骤,并说明脚本应该恢复还是停止。

继续阅读:错误与调试——异常如何发生和传递

动手完成一次读取与写回

  1. 创建本章开头的 orders.json
  2. 使用 json.load() 读取三条订单。
  3. orders[0]["status"] 打印出来,确认已经得到 Python 对象。
  4. 调用 summarize_paid_orders(orders),确认数量为 2、金额为 328.0
  5. 使用 json.dump() 把汇总写到 paid_orders_summary.json
  6. 打开结果文件,确认中文没有变成转义字符。

写回步骤可以直接使用:

with open("paid_orders_summary.json", "w", encoding="utf-8") as file:
    json.dump(
        summarize_paid_orders(orders),
        file,
        ensure_ascii=False,
        indent=2,
    )