Dict lồng nhau
Bạn sẽ học được
- Đọc và ghi dict nhiều tầng.
- Chuỗi
get()an toàn cho trường thiếu. - Duyệt cấu trúc lồng: items, list of dict.
- Tính toán trên dữ liệu lồng như đơn hàng và khách hàng.
- Cái bẫy sao chép nông và cách làm phẳng dữ liệu.
Khái niệm cốt lõi
Dữ liệu thực tế thường lồng nhau: một đơn hàng có khách hàng, địa chỉ, danh sách sản phẩm. Dict lồng nhau là cách tự nhiên để biểu diễn:
order = {
"id": 1001,
"customer": {"name": "An", "email": None, "address": {"city": "Hà Nội"}},
"items": [
{"sku": "A1", "qty": 2, "price": 150000},
{"sku": "B2", "qty": 1, "price": 450000},
],
}
print(order["customer"]["name"])AnMỗi cặp ngoặc vuông đi xuống một tầng. order["customer"] là dict, tiếp ["name"] là chuỗi. Có thể đi sâu bao nhiêu tầng tuỳ cấu trúc.
Đọc trường có thể thiếu bằng [] sẽ ném KeyError nếu bất kỳ tầng nào không có:
print(order["customer"].get("email"))
print(order.get("customer", {}).get("address", {}).get("city", "không rõ"))None
Hà NộiDòng thứ hai là chuỗi get an toàn: mỗi tầng dùng get với {} làm mặc định, nên thiếu tầng nào cũng không lỗi và cuối cùng rơi về "không rõ". Dài dòng, nhưng đúng cho dữ liệu từ API hoặc JSON chưa kiểm chứng.
Nếu cần dùng nhiều lần, viết hàm:
def lay(data, *keys, default=None):
for key in keys:
if not isinstance(data, dict) or key not in data:
return default
data = data[key]
return data
print(lay(order, "customer", "address", "city"))
print(lay(order, "customer", "phone", default="chưa có"))Hà Nội
chưa có*keys nhận số đường dẫn tuỳ ý; vòng lặp dừng ngay khi gặp tầng không phải dict hoặc thiếu khóa. Đây là phiên bản đơn giản của "safe navigation" mà nhiều ngôn ngữ có sẵn (?.).
Duyệt dữ liệu lồng
Với list các item bên trong:
total = sum(item["qty"] * item["price"] for item in order["items"])
print(total)750000Generator đi qua từng dict con, nhân số lượng với đơn giá. Kết quả 2 × 150000 + 1 × 450000 = 750000.
In chi tiết:
for item in order["items"]:
print(item["sku"], item["qty"] * item["price"])A1 300000
B2 450000Với dict của dict — ví dụ doanh thu theo tháng, mỗi tháng lại theo kênh — duyệt hai tầng:
revenue = {
"2026-01": {"web": 120, "store": 80},
"2026-02": {"web": 150, "store": 95},
}
for month, channels in revenue.items():
total = sum(channels.values())
print(month, total)2026-01 200
2026-02 245items() ở tầng ngoài cho cặp tháng → dict kênh; .values() ở tầng trong cộng các kênh.
Ghi và cập nhật
Gán vào khóa lồng phải đi đủ đường:
order["customer"]["phone"] = "0901234567"
order["customer"]["address"]["district"] = "Cầu Giấy"Nếu tầng giữa chưa tồn tại, dòng trên ném KeyError. Với dữ liệu xây dần, dùng setdefault:
by_month = {}
for row in [("2026-01", "web", 120), ("2026-01", "store", 80)]:
month, channel, amount = row
by_month.setdefault(month, {})[channel] = amount
print(by_month){'2026-01': {'web': 120, 'store': 80}}Hoặc defaultdict(dict) từ collections.
Cái bẫy sao chép nông
Sao chép dict bằng .copy() chỉ sao tầng ngoài; các dict con vẫn dùng chung:
a = {"customer": {"name": "An"}}
b = a.copy()
b["customer"]["name"] = "Bình"
print(a){'customer': {'name': 'Bình'}}Sửa b làm đổi a. Đây cùng bản chất với list lồng ở chương 3. Muốn độc lập hoàn toàn:
import copy
b = copy.deepcopy(a)Với dữ liệu chỉ đọc, sao chép nông thường đủ và rẻ hơn. Với dữ liệu sẽ sửa, hãy nghĩ tới deepcopy hoặc tạo cấu trúc mới thay vì sửa tại chỗ.
Làm phẳng để phân tích
Dict lồng tiện cho API nhưng khó tính toán. Bước làm phẳng (flatten) đưa về bảng:
rows = []
for item in order["items"]:
rows.append({
"order_id": order["id"],
"customer": order["customer"]["name"],
"city": order["customer"]["address"]["city"],
"sku": item["sku"],
"revenue": item["qty"] * item["price"],
})Mỗi dòng là một item kèm thông tin đơn và khách. Dạng này tương thích với CSV, pandas, SQL — nơi mọi phân tích đều bắt đầu từ bảng phẳng.
Ngược lại, khi dựng JSON gửi API, gom từ bảng phẳng về cấu trúc lồng bằng setdefault hoặc groupby của pandas.
Thực hành
- Đọc
order["customer"]["name"], thử đọc khóa không tồn tại để thấyKeyError. - Dùng chuỗi
get()lấycityvà một trường thiếu với mặc định. - Viết hàm
lay(data, *keys)và thử với đường dẫn sai. - Tính tổng tiền các item bằng generator.
- Duyệt dict hai tầng doanh thu theo tháng/kênh.
- Làm phẳng
orderthành list dict và in từng dòng.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
KeyError giữa chuỗi | Tầng trước không có khóa | Dùng get từng tầng |
TypeError: 'NoneType' object is not subscriptable | get trả None rồi tiếp tục [...] | Mặc định {} trong get |
| Sửa bản sao, bản gốc đổi | Sao chép nông | deepcopy |
AttributeError trên dict | Dùng d.key thay d["key"] | Dùng ngoặc vuông |
| Tổng sai | Cộng chuỗi hoặc thiếu item | Ép kiểu, kiểm tra len |
setdefault ghi đè | Gán thay vì cộng dồn | += giá trị hiện có |
| Dữ liệu lồng không đưa vào bảng được | Chưa flatten | Tạo list dict phẳng |
Tóm tắt
- Dict lồng truy cập bằng chuỗi ngoặc vuông; thiếu tầng sẽ
KeyError. get(k, {})nối tầng an toàn; hàmlaygói lại cho gọn.- Duyệt bằng
items(); list con bằng generator. .copy()chỉ nông — dùngdeepcopykhi cần độc lập.- Làm phẳng về list dict trước khi phân tích hoặc ghi CSV.
Use ← → to move between lessons