Try   HackMD

csv檔案讀寫程式

難度:★★進階

致謝:本文站在文山社大Oliver同學肩膀上開展。沒有Oliver先前對csv檔案的整理報告,即無拙文。感謝Oliver。

csv檔案功能、格式等略,不清楚的讀者請自行求教Google老師。以下單刀直入立馬介紹csv讀寫。

csv檔案讀取

# -*- coding: utf-8 -*- import csv # Python standard library中的csv模組。 # 讀取csv檔內容,放到csvDataToRead變數中。 csvFileToRead = open('d:\\python\\member.csv', 'r', encoding='utf-8-sig') csvDataToRead = csv.reader(csvFileToRead) # 將csvDataToRead(原csv檔內容)轉為list。 dataList = list(csvDataToRead) # 注意:必須在轉完list之後才關檔,否則會產生'ValueError: I/O operation on closed file.' #    的exception。 csvFileToRead.close() print(dataList) # 修改list內容。 dataList.append(['Alice', 521, '自學']) dataList[1][0] = 'Rebecca' print(dataList)

程式重點在第9列:

# 將csvDataToRead(原csv檔內容)轉為list。 dataList = list(csvDataToRead)

運用Python standard library中的csv模組讀取csv檔案,可輕易將檔案內容轉為Python內建的list或tuple型態。轉成了這些型態(尤其是list),後續的查詢、修改、新增、刪除等常見資料存取作業,就變得非常簡單。

試想一下:假如沒有csv模組(或其他類似third party)可用,要怎樣讀取csv檔呢?很可能拿文字檔I/O充數。程式大概如下:

csvFileToRead = open('d:\\python\\member.csv', 'r', encoding='utf-8-sig') plainText = csvFileToRead.read()

plainText就是檔案內容。不過那是真"plain text",普通字串耳。轉換list/tuple,行,可惜轉出來的list/tuple完全走調。請看下面的程式碼和輸出:

csvFileToRead = open('d:\\python\\member.csv', 'r', encoding='utf-8-sig') plainText = csvFileToRead.read() dataList = list(plainText) # 將字串轉為list。 print(dataList)

原來的csv檔:

用一般文字檔方式讀檔,轉出的list亂七八糟,不成人樣。一看即知那不是正確的結果。

用csv模組讀檔,轉出的list才是我們想要的樣子。

拿csv當一般文字檔來讀,又想轉出正確的list,大概得自行寫csv parser。不算太難,卻煩。功力不足寫出的parser肯定:beetle:一堆。

講到這裡,大家清楚csv模組的功效了吧?像parsing這種dirty work,模組會替我們代勞,乖巧勤快又品質保證。這麼好用的工具豈能擱著讓它:zzz:


讀取csv檔有兩點補充:

  1. 在Windows平台,csv檔如採utf-8編碼,無論有BOM無BOM,亦不管內容是純英數,或中、泰、韓、日文,open()開檔方法都須加上encoding='utf-8-sig'參數
    ​​​​open('d:\\python\\member.csv', 'r', encoding='utf-8-sig')
    但csv如為Big5編碼的中文檔案,則勿加encoding參數,或者encoding'Big5', 'cp950',甚至None
    ​​​​open('d:\\python\\member.csv', 'r')
  2. csv欄位轉為list後,list所有元素的型態都是str。各種不同內容測試結果如下(組合太多,未能窮舉):
No
csv檔欄位內容
紅字表重要
範例
轉換後的list元素
備註
1 整數 123 '123' 不管文字數字,都
轉換成str。
2 浮點數 -.805 '-.805' 同上。
3 Boolean True 'True' 仍然是字串,不會
自動轉為True
4 全欄無引號 鳳閣恩仇未了情 '鳳閣恩仇未了情' 就是字串。
5 前後綴雙引號"" "紫釵記" '紫釵記' 雙引號去除,效果
等於全欄無引號。
6 前後綴單引號'' '胡不歸' "'胡不歸'" 前後單引號均保留。
7 前後均無引號,中插單引號 A Bug's Life "A Bug's Life" 單引號不須以\
跳脫。
8 前後綴雙引號,中插單引號 "A Bug's Life" "A Bug's Life" 同上。
9 前後綴雙引號,中置逗點, "男燒衣,女燒衣", 1975, 白駒榮,杜煥 ['男燒衣,女燒衣', ' 1975', ' 白駒榮', '杜煥'] "男燒衣,女燒衣"
為一欄。list只有4個元素。
注意:原字串中的
1975白駒榮分別
和其前面的,間有
一空白,轉出來的
list元素隨之前置
空白。而原杜煥
面並無空格,list
亦未留白。
10 前後綴單引號,中置逗點 '男燒衣,女燒衣', 1975, 白駒榮,杜煥 ["'男燒衣", " 女燒衣'", ' 1975', ' 白駒榮', '杜煥'] '男燒衣,女燒衣'
為兩欄。list共有5個元素。
11 欄首一段包雙引號 "萬惡"淫為首 '萬惡淫為首' 雙引號刪除。
12 欄中一段包雙引號 萬惡"淫"為首 '萬惡"淫"為首' 雙引號保留。
13 欄末一段包雙引號 萬惡淫為"首" '萬惡淫為"首"' 雙引號保留。
14 首末各一段包雙
引號
"萬惡"淫為"首" '萬惡淫為"首"' 欄首雙引號去除,欄末保留。
15 欄首一段包單引號 '萬惡'淫為首 "'萬惡'淫為首" 單引號保留。
16 欄中一段包單引號 萬惡'淫'為首 "萬惡'淫'為首"' 單引號保留。
17 欄末一段包單引號 萬惡淫為'首' "萬惡淫為'首'" 單引號保留。
18 首末各一段包單
引號
'萬惡'淫為'首' "'萬惡'淫為'首'" 單引號全保留。
19 前單引號,後雙
引號
'雷鳴金鼓戰笳聲" '\'雷鳴金鼓戰笳聲"' 單雙引號均保留。單引號以\跳脫(escape)。
20 欄中分插雙單引號 大鬧"廣昌'隆 '大鬧"廣昌\'隆' 同上。
21 前綴雙引號,但
無相對應的後綴
雙引號
"重慶森林,1994,王家衛 ↩️ 悲情城市,1989, 侯孝賢 ↩️ 霸王別姬,1993, 陳凱歌 [['重慶森林,1994,王家衛\n悲情城市,1989, 侯孝賢\n霸王別姬,1993, 陳凱歌']] 系統誤判。原資料3筆各3欄,list誤作1筆1欄。
22 中插單一雙引號 重慶"森林,1994,王家衛 ↩️ 悲情城市,1989, 侯孝賢 ↩️ 霸王別姬,1993, 陳凱歌 [['重慶"森林', '1994', '王家衛'], ['悲情城市', '1989', ' 侯孝賢'], ['霸王別姬', '1993', ' 陳凱歌']] list正確列出
3筆3欄,無誤判。
雙引號保留。

csv檔案寫入

# -*- coding: utf-8 -*- import csv # Python standard library中的csv模組。 dataList = [['Alex', 1, True], ['Becca', 2, False], ['Thomas', 4, True]] # 將list內容寫入到副檔名為.csv的檔案。 # 注意:檔案寫入時,open()方法要加上newline=''參數。否則生出來的csv檔用Excel開啟,每筆 #    資料之間會有一空列。 csvFileToWrite = open(r'd:\python\member1.csv', 'w', encoding='utf-8', newline='') # 應用csv模組的writer()方法。 csvDataToWrite = csv.writer(csvFileToWrite) # 逐列寫入。 for row in dataList: csvDataToWrite.writerow(row) # 注意:必須在寫完之後才關檔,否則會產生'ValueError: I/O operation on closed file.' #    的exception。 csvFileToWrite.close()

使用csv模組寫檔,同樣享受操作方便和資料正確的好處。以上程式輸出:

用Excel開啟。正確無誤:

假設以普通文字檔方式寫檔,程式碼大概會是:

# -*- coding: utf-8 -*- # 用一般文字檔寫入方式。 dataList = [['Alex', 1, True], ['Becca', 2, False], ['Thomas', 4, True]] # open()還是得加newline=''參數。 csvFileToWrite = open(r'd:\python\plainText.csv', 'w', encoding='utf-8', newline='') # 逐列寫入。 for row in dataList: csvFileToWrite.write(str(row)) csvFileToWrite.write('\r\n') # \r\n是換列符號,即16進位的0D 0A。 csvFileToWrite.close() # 寫入完畢記得關檔。

寫入固然成功。但plainText.csv卻長這個模樣:

用Excel開啟:

資料滲沙挾石,雜草叢生。

一言蔽之,檔案寫入也請採用csv模組,別重新發明輪子。


另外,寫檔須注意一點:open()方法要加上newline=''參數。

# 下面的open()方法未加newline=''參數。 csvFileToWrite = open(r'd:\python\member1.csv', 'w', encoding='utf-8')

以上程式碼未加這參數,後果是:產生的csv檔在換列處會多製造一個carriage return符號,即16進位的0D。本來Windows文字檔換列碼是16進位的0D 0A,多一個c/r,變成0D 0D 0A。以Windows的notepad「記事本」瀏覽,看似正常。用notepad++開啟,則看到兩列間插入一空列:

Excel開檔亦如此:

所以,欲掃除文盲,不,是掃除空列,寫檔毋忘newline='',除非真想製造空列效果。

結論

建議無論是讀是寫,都恭請standard library的csv模組大爺出馬,勿土法練鋼拿一般文字檔I/O勉強湊合。文字檔I/O對象是普通文件。對csv這樣有格式要求的檔案,引進專屬模組才事半功倍。

「工欲善其事,必先利其器」。


外部參考資料:
如何使用Excel開啟UTF-8格式的CSV檔案

tags: csv 文字檔 讀檔 寫檔 standard library csv模組 newline