會計要一份 CSV,可「拿鐵,燕麥」這個名字會撐破所有天真的 split。csv 模組懂引號。DictReader 對第一行做了什麼假設?
CSV 看起來簡單,直到某個值裡出現逗號、引號或換行。csv 模組會處理這些細節,還能把每一行表示成以表頭為鍵的字典。小例子可以用 StringIO,避免留下臨時檔案。 你還會處理列數不齊、嚴格寫入和位元組順序標記。
把行讀成字典
預設情況下,DictReader 把第一行當作欄位名。之後每一行都會變成字典。相比記住列的位置,這種方式更易讀,列很多時尤其明顯。
import csv
from io import StringIO
raw = "name,score\nAda,92\nGrace,88\n"
rows = list(csv.DictReader(StringIO(raw)))
print(rows[0]["name"])表頭名稱變成鍵
用 writer 寫入行
csv.writer 一次寫入一行,並自動加上行結束符。傳入欄位列表或元組即可。小型記憶體範例可以寫入 StringIO,再用 getvalue() 查看結果。
import csv
from io import StringIO
buffer = StringIO()
writer = csv.writer(buffer)
writer.writerow(["name", "score"])
writer.writerow(["Ada", 92])
print(buffer.getvalue().strip())在記憶體中建構 CSV 文字
給需要的欄位加引號
欄位中的逗號必須用引號保護。QUOTE_ALL 會給每個欄位都加引號,適合另一個工具要求這種格式的場景。讀取時 csv 會去掉引號,還原原始值。
import csv
from io import StringIO
buffer = StringIO()
writer = csv.writer(buffer, quoting=csv.QUOTE_ALL)
writer.writerow(["a,b", "c"])
print(buffer.getvalue().strip())欄位內部的逗號
DictReader:缺失列和多餘列
DictReader 把表頭當鍵。短行缺少的欄位會得到 None;多出來的欄位會放進 restkey 指定的鍵。邊界處要認真檢查這兩種情況,不能假設每一行都正好是預期形狀。
import csv
import io
text = "name,price\nlatte,4.5\ntea\nmocha,5.0,note\n"
reader = csv.DictReader(io.StringIO(text), restkey="extra")
for row in reader:
print(row["name"], row.get("price"), row.get("extra"))有表頭不代表每行都完整
DictWriter:固定列序,嚴格拒絕多餘欄位
fieldnames 固定列順序。extrasaction='raise' 會把意外欄位變成錯誤,而不是悄悄丟掉。固定的 lineterminator 讓輸出可復現,也能避免 Windows 上出現雙換行。
import csv
import io
out = io.StringIO(newline="")
fields = ["name", "price"]
writer = csv.DictWriter(
out, fieldnames=fields, extrasaction="raise", lineterminator="\n"
)
writer.writeheader()
writer.writerow({"name": "latte", "price": 4.5})
try:
writer.writerow({"name": "tea", "price": 3.0, "note": "hot"})
except ValueError as error:
print(type(error).__name__, error)
print(repr(out.getvalue()))多一列就明確報錯
在邊界處理 BOM 和換行
表格軟體導出的 CSV 經常帶 UTF-8 位元組順序標記。用 utf-8-sig 解碼,第一個表頭才不會變成 \ufeffname。文字流用 newline='' 打開,讓 csv 模組而不是作業系統決定行尾。
import csv
import io
raw = b"\xef\xbb\xbfname,price\r\nlatte,4.5\r\n"
text = raw.decode("utf-8-sig")
reader = csv.DictReader(io.StringIO(text, newline=""))
row = next(reader)
print(row["name"], row["price"])
out = io.StringIO(newline="")
writer = csv.writer(out, lineterminator="\n")
writer.writerow(["tea", 3.0])
print(repr(out.getvalue()))先解碼 BOM,再解析
本課要點
- 使用 csv.reader 或 csv.DictReader,不要自己按逗號拆分。
- DictReader 預設把第一行當作表頭。
- writerow 寫入一行並添加行結束符。
- 引號可以保護欄位內的逗號、引號和換行。
- 小型記憶體範例可以使用 StringIO。
- 檢查行列形狀;寫入時拒絕多餘欄位;用 utf-8-sig 和 newline='' 控制邊界。
常見錯誤
- 欄位包含逗號時仍然使用 line.split(",")。
- 忘記 DictReader 讀出的值預設是字串,需要時得轉換。
- 在會轉換行結束符的平臺上寫 CSV 檔案時,忘記 newline=""。
- 打開 CSV 文字時不用 newline='',然後把空行怪到 csv 頭上。
用 DictReader 解析 'name,score\nAda,92\n',再列印 Ada 的分數。
動手試試
改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。
練習本課
12 道題,作答後立即看到解釋。