M10 選擇格式
- 說出 JSON、YAML、CSV、TOML 各自適合甚麼,按用途選擇;
- 用
csv模組讀寫 CSV,知道讀入的值一律是字串,處理 Excel 的 BOM; - 用
tomllib讀 TOML,認出pyproject.toml就是 TOML; - 寫一份 JSON Schema,用
jsonschema驗證資料,看懂它的錯誤訊息。
1同一份資料,四種寫法 Four formats, one dataset
小書店的兩本書,分別用四種格式寫:
四種格式最大的分別,在於它們為誰而設計:
| 格式 | 為誰而設計 | 擅長 | 不擅長 |
|---|---|---|---|
| JSON | 程式之間交換資料 | 嚴格、每種語言都讀得、網頁 API 的標準 | 不能寫註解;人手寫容易漏逗號(M8) |
| YAML | 人手寫設定 | 簡潔、可寫註解、可巢狀 | 會猜型別(M9);縮排錯了不一定報錯 |
| CSV | 表格、試算表 | Excel 直接開;一行一筆,簡單 | 只有字串、不能巢狀、沒有標準的型別 |
| TOML | 工具的設定檔 | 型別明確、可寫註解、不猜型別 | 深層巢狀寫起來囉嗦;Python 內建只能讀 |
2CSV:表格 CSV for tables
CSV(comma-separated values)每一行是一筆記錄,用逗號分隔各欄,第一行通常是欄名。它沒有「巢狀」:書的標籤是一個 list,只能自己約定一種寫法擠進一格,例如 python;json。
標準函式庫的 csv 模組負責處理引號、逗號這些細節。DictReader 把每一行變成一個 dict:
import csv
with open("books.csv", encoding="utf-8", newline="") as f:
rows = list(csv.DictReader(f))
rows[0] # {'書名': '看得見的資料', '價錢': '120', '庫存': '3', '標籤': 'python;json'}
留意價錢是 '120':CSV 讀入的值一律是字串,要計算就要自己轉換(M3)。寫出時用 DictWriter;值裏有逗號,它會自動加上引號:"Python, 入門",120。
csv 模組自己處理換行,open() 要加 newline=""。不加的話,在 Windows 上寫出的 CSV 每行之間會多一行空行。
Excel 存成「CSV UTF-8」的檔案,開頭有三個看不見的位元組,叫 BOM。用 encoding="utf-8" 讀,第一個欄名會變成 '\ufeff書名',用 row["書名"] 就找不到。讀 Excel 存的 CSV,用 encoding="utf-8-sig",它會把 BOM 拿走。反過來,要讓 Excel 正確顯示中文,寫出時也用 utf-8-sig。
M11 會用 pandas 處理 CSV,屆時型別轉換會方便得多;但理解「CSV 只有字串」仍然重要。
3TOML:設定檔 TOML for configuration
TOML 是另一種給人寫的設定格式。你其實已經見過它:M1 的 pyproject.toml 就是 TOML。
title = "小書店目錄"
updated = 2026-10-04 # 日期是 TOML 本身的型別
[[book]] # [[ ]]:一串同類的表,讀入是 list of dict
title = "看得見的資料"
price = 120
tags = ["python", "json"]
TOML 和 YAML 最大的分別:字串一定要加引號,所以它不用猜。M9 那些陷阱,在 TOML 裏多數會直接報錯,而不是靜靜地猜錯:
| 寫的 | YAML(PyYAML) | TOML(tomllib) |
|---|---|---|
country = NO |
False |
報錯:Invalid value |
phone = 0123456 |
42798 |
報錯 |
open = 9:30 |
570 |
報錯 |
version = 1.10 |
1.1 |
1.1(一樣要加引號) |
報錯比猜錯好:你立即知道要改,而不是等到挪威的客人下不到單。
Python 3.11 起,標準函式庫有 tomllib,只能讀,不能寫:
import tomllib
with open("catalog.toml", "rb") as f: # 注意:用二進制模式 "rb" 開
data = tomllib.load(f)
要寫出 TOML,要另外安裝套件(例如 tomli-w)。這也說明了 TOML 的定位:給人寫、給工具讀的設定檔,而不是程式之間交換資料。
4怎樣選 How to choose
flowchart TD
Q1{"主要是誰寫?"} -->|"程式寫、程式讀"| J["JSON"]
Q1 -->|"人手寫"| Q2{"是表格嗎?<br/>(一行一筆、欄位固定)"}
Q2 -->|"是,而且要用 Excel 開"| C["CSV"]
Q2 -->|"不是,是設定"| Q3{"有工具指定格式嗎?"}
Q3 -->|"有(pyproject.toml、<br/>GitHub Actions…)"| U["用它指定的"]
Q3 -->|"沒有"| Q4{"層次深、<br/>要寫很多註解?"}
Q4 -->|"是"| Y["YAML(記得加引號)"]
Q4 -->|"否"| T["TOML"]
classDef q fill:#ffffff,stroke:#d3d1cb,color:#37352f
classDef a fill:#f1f1ef,stroke:#d3d1cb,color:#37352f
class Q1,Q2,Q3,Q4 q
class J,C,U,Y,T a
實際上,很多時候格式不是你選的:網頁 API 給你 JSON、會計部給你 Excel 匯出的 CSV、pyproject.toml 一定是 TOML、GitHub Actions 一定是 YAML。所以四種都要讀得懂,而自己能選的時候,按上圖選。
5JSON Schema:把「應該長甚麼樣」寫下來 JSON Schema
M9 寫過一個 check_types(),用一個 dict 寫下每個設定應有的型別。這個想法有一個標準的寫法,叫 JSON Schema:用 JSON 本身描述「一份資料應該長甚麼樣」。
book_schema = {
"type": "object",
"properties": {
"title": {"type": "string"},
"price": {"type": "number", "minimum": 0},
"stock": {"type": "integer", "minimum": 0},
"tags": {"type": "array", "items": {"type": "string"}},
},
"required": ["title", "price", "stock"],
}
先在專案裏加入驗證用的套件:uv add jsonschema。然後:
import jsonschema
jsonschema.validate(book, book_schema) # 合格:甚麼都不發生
不合格時,它報 ValidationError,訊息說明是哪一項、為甚麼:
'120' is not of type 'number' ← price 是字串(例如從 CSV 讀入)
'stock' is a required property ← 缺了 stock
-1 is less than the minimum of 0 ← 庫存是負數
2 is not of type 'string' ← tags 的第二項不是字串
錯誤物件的 absolute_path 指出出事的位置。它不是普通的 list,用 list(err.absolute_path) 轉換後再看,例如 ['tags', 1] 即「tags 的第 1 項(第二項)」,和 M4 的路徑同一個讀法。
JSON Schema 不只檢查 JSON:YAML、TOML、CSV 讀入 Python 之後都是 dict 和 list,同一份 schema 照樣可以檢查。
'120' is not of type 'number' 的意思是「這不合格」,不是「我幫你轉成數字」。從 CSV 讀入的資料,先自己轉換型別,再用 schema 檢查轉換的結果。
6Schema 常用的規則 Common schema keywords
| 規則 | 意思 | 例子 |
|---|---|---|
type |
型別:object、array、string、number、integer、boolean、null |
{"type": "integer"} |
properties |
物件裏每個鍵的規則 | 見上 |
required |
一定要有的鍵 | ["title", "price"] |
items |
陣列裏每一項的規則 | {"type": "string"} |
minimum、maximum |
數字的範圍 | {"minimum": 0} |
enum |
只准這幾個值 | {"enum": ["HK", "TW", "NO"]} |
pattern |
字串要符合的樣式 | {"pattern": "^[A-Z]{2}$"}:兩個大寫字母 |
additionalProperties |
是否准許沒有列出的鍵 | false:打錯了鍵名也會被抓到 |
"additionalProperties": false 特別有用:設定檔裏把 price 打成 prcie,沒有它,schema 只會說「缺了 price」;有了它,還會說「多了一個 prcie」,一眼看出是打錯字。
7容易混淆 Easily confused
| 這兩樣 | 分別 |
|---|---|
| JSON 和 JSON Schema | 前者是資料;後者是描述資料應該長甚麼樣的規則,本身也用 JSON 寫。 |
CSV 的 "120" 和 JSON 的 120 |
CSV 讀入一律是字串;JSON 有數字型別。 |
utf-8 和 utf-8-sig |
後者會處理開頭的 BOM;讀寫 Excel 的 CSV 用它。 |
tomllib 讀和寫 |
標準函式庫只能讀;寫要另外安裝套件。 |
number 和 integer |
JSON Schema 裏,integer 不接受 2.5;number 兩者都接受。 |
| 驗證和轉換 | Schema 只說合不合格,不會改你的資料。 |
8動手:一份書單,四種格式 Hands-on: one list, four formats
- 下載
m10-four-formats.ipynb和m10-booklist.zip,放進python-course資料夾; - 在終端機執行
uv add jsonschema; - 執行
uv run jupyter lab,打開 notebook,逐格執行。
zip 裏是同一份書單的四個版本,其中 CSV 是會計部用 Excel 存的。你要把四份讀入、轉成同一個樣子、比較它們是否真的相同,再寫一份 JSON Schema 檢查全部書,找出混在裏面的壞資料。
m10-sensor-schema-eng.ipynb,需要 M4 的 sensors.json:為感測器資料寫一份 JSON Schema(量程用 enum、單位、讀數的範圍),用它檢查幾份故意弄壞的資料,並用 iter_errors 一次列出所有錯誤。
第三部分完結。M11 開始應用:用 pandas 把一層層的 JSON 攤平成表格,按月統計訂單、畫圖。