第 17 课 · 进阶 · 19 分钟

Python 嵌套数据

像处理 JSON 一样,一层一层地读取数据。

字典列表嵌套键求和

订单不是一行扁平的数据:它有明细,每条明细还有加料。想一步跳到底,KeyError 就是这么来的。应该先检查哪一层?

真实数据很少能塞进一个列表或字典里。订单包含商品,接口响应包含对象,记录包含字段。给每一层命名,并一次只访问一层,嵌套数据就会清楚很多。 你还会逐层校验、把嵌套记录摊平成行,并用显式栈遍历树。

字典组成的列表

外层列表保存多条记录,每个字典保存一条记录的字段。遍历列表,再从每个字典里读取键。很多 JSON 文件都是这种形状。

orders = [
    {"drink": "latte", "price": 4.5},
    {"drink": "tea", "price": 3.0},
]
for order in orders:
    print(order["drink"], order["price"])

列表中的记录

读取嵌套键

每一层用一对方括号。data["shop"] 返回内层字典,再用 ["name"] 读取字段。路径变长时,可以先把中间结果存进变量。

data = {"shop": {"name": "Corner", "open": True}}
print(data["shop"]["name"])
print(data["shop"]["open"])

两层键

更新字段并计算总和

嵌套值仍然是普通的字典值,所以赋值方式不变。要对某个字段求和,就遍历外层列表,从每条记录里读取该键。能用循环表达意图时,不要硬编码索引。

orders = [{"price": 3.0}, {"price": 4.5}]
orders[0]["price"] = 3.5
total = sum(order["price"] for order in orders)
print(total)

更新并求和

每一层都要校验

嵌套读取是一串假设。每一层都检查容器类型和键,或者先用 get() 给空默认值再索引。在边界做一次校验,比从三个地方分别接住 TypeError 和 KeyError 便宜。

payload = {"shop": {"name": "Corner", "orders": [{"total": 9.0}]}}
shop = payload.get("shop", {})
orders = shop.get("orders", []) if isinstance(shop, dict) else []
first_total = orders[0].get("total") if orders and isinstance(orders[0], dict) else None
print(shop.get("name", "unknown"))
print(first_total)
print(payload.get("missing", {}).get("name", "unknown"))

先检查容器,再读键

把嵌套记录摊平成行

嵌套订单适合存储,却不适合直接汇报。一次摊平一层:外层订单、内层商品,再生成同时带两个标识的行。缺失值要显式保留,不要悄悄丢掉整行。

orders = [
    {"id": 1, "items": [{"name": "latte", "qty": 2}]},
    {"id": 2, "items": [{"name": "tea", "qty": 1}, {"name": "mocha", "qty": 3}]},
]
rows = []
for order in orders:
    for item in order.get("items", []):
        rows.append((order["id"], item["name"], item.get("qty", 0)))
for row in rows:
    print(row)

每层一个循环,保留父级 id

用显式栈遍历树

嵌套可能很深时,显式栈能避开递归深度限制,也让待处理的工作看得见。想从左到右访问,就按相反顺序压入子节点;栈是后进先出。

tree = {"name": "menu", "children": [
    {"name": "drinks", "children": [{"name": "latte"}, {"name": "tea"}]},
    {"name": "food", "children": [{"name": "toast"}]},
]}
stack = [tree]
while stack:
    node = stack.pop()
    print(node["name"])
    stack.extend(reversed(node.get("children", [])))

栈保存还没处理的工作

本课要点

  • 嵌套数据要一层一层地用方括号读取。
  • 字典列表是常见的记录集合。
  • 给内层字典的键赋值,就能更新字段。
  • 用循环或生成器表达式汇总某个字段。
  • 嵌套数据每层都要校验;显式栈让树遍历更可控。

常见错误

  • 值还是字典时,只写了一层方括号。
  • 不检查记录,就假设每条记录都有同样的键。
  • 硬编码索引,而不是遍历记录。
  • 子节点压入栈的顺序反了,却奇怪输出为什么倒过来。
轮到你

创建两个订单字典,放进列表,然后打印总价。

动手试试

改改变量、先预测输出,再运行代码。第一次点击“运行代码”时会下载 Python 运行时,可能需要几秒钟。

浏览器内 Python本地运行 · 无需账号
首次运行会从 CDN 下载 Pyodide。修改后点击“运行代码”。

练习本课

12 道题,作答后立即看到解释。

查看 12 道题答案开始练习
← 模块与导入lambda 与 map →