# <font face='consolas'><font size=5 color=#000080><center>**csv檔案讀寫程式**</center></font>
難度:★★進階
:::info
致謝:本文站在文山社大Oliver同學肩膀上開展。沒有Oliver先前對csv檔案的整理報告,即無拙文。感謝Oliver。
:::
csv檔案功能、格式等略,不清楚的讀者請自行求教Google老師。以下單刀直入立馬介紹csv讀寫。
## csv檔案讀取
```python=
# -*- 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)
```
<font color=red><b>程式重點在第9列:</b></font>
```python=8
# 將csvDataToRead(原csv檔內容)轉為list。
dataList = list(csvDataToRead)
```
運用Python standard library中的csv模組讀取csv檔案,可輕易將檔案內容轉為Python內建的list或tuple型態。<font color=red><b>轉成了這些型態(尤其是list),後續的查詢、修改、新增、刪除等常見資料存取作業,就變得非常簡單。</b></font>
試想一下:假如沒有csv模組(或其他類似third party)可用,要怎樣讀取csv檔呢?很可能拿文字檔I/O充數。程式大概如下:
```python=
csvFileToRead = open('d:\\python\\member.csv', 'r', encoding='utf-8-sig')
plainText = csvFileToRead.read()
```
plainText就是檔案內容。不過那是真"plain text",普通字串耳。轉換list/tuple,行,可惜轉出來的list/tuple完全走調。請看下面的程式碼和輸出:
```python=
csvFileToRead = open('d:\\python\\member.csv', 'r', encoding='utf-8-sig')
plainText = csvFileToRead.read()
dataList = list(plainText) # 將字串轉為list。
print(dataList)
```
原來的csv檔:
![](https://i.imgur.com/IvCsame.png =x100)
用一般文字檔方式讀檔,轉出的list亂七八糟,不成人樣。一看即知那不是正確的結果。
![](https://i.imgur.com/G38VPZz.png =720x)
用csv模組讀檔,轉出的list才是我們想要的樣子。
![](https://i.imgur.com/smhfahL.png =720x)
拿csv當一般文字檔來讀,又想轉出正確的list,大概得自行寫csv parser。不算太難,卻煩。功力不足寫出的parser肯定:beetle:一堆。
講到這裡,大家清楚csv模組的功效了吧?像parsing這種dirty work,模組會替我們代勞,乖巧勤快又品質保證。這麼好用的工具豈能擱著讓它:zzz:?
---
讀取csv檔有兩點補充:
1. 在Windows平台,csv檔如採utf-8編碼,[無論有BOM無BOM,亦不管內容是純英數,或中、泰、韓、日文,open()開檔方法都須加上`encoding='utf-8-sig'`參數](https://hackmd.io/s/BkN-q-Vob):
```python=
open('d:\\python\\member.csv', 'r', encoding='utf-8-sig')
```
但csv如為Big5編碼的中文檔案,則勿加`encoding`參數,或者`encoding`用`'Big5'`, `'cp950'`,甚至`None`:
```python=
open('d:\\python\\member.csv', 'r')
```
2. csv欄位轉為list後,list所有元素的型態都是str。各種不同內容測試結果如下(組合太多,未能窮舉):
|<center>**No**</center>|<center>**csv檔欄位內容</br><font color=red>紅字</font>表重要**</center>|<center>**範例**</center>|<center>**轉換後的list元素**</center>|<center>**備註**</center>|
|:---|:------|:-----------|:-----------|:-----------|
|<font color=red>**1**</font>|<font color=red>**整數**</font>|`123`|`'123'`|不管文字數字,都</br>轉換成str。|
|2|浮點數|`-.805`|`'-.805'`|同上。|
|3|Boolean|`True`|`'True'`|仍然是字串,不會</br>自動轉為`True`。|
|<font color=red>**4**</font>|<font color=red>**全欄無引號**</font>|`鳳閣恩仇未了情`|`'鳳閣恩仇未了情'`|就是字串。|
|<font color=red>**5**</font>|<font color=red>**前後綴雙引號`""`**</font>|`"紫釵記"`|`'紫釵記'`|<font color=red>**雙引號去除,效果</br>等於全欄無引號。**</font>|
|<font color=red>**6**</font>|<font color=red>**前後綴單引號`''`**</font>|`'胡不歸'` |`"'胡不歸'"`| 前後單引號均保留。 |
|<font color=red>**7**</font>|<font color=red>**前後均無引號,中插單引號**</font>|`A Bug's Life`|`"A Bug's Life"`|單引號不須以`\`</br>跳脫。|
|<font color=red>**8**</font>|<font color=red>**前後綴雙引號,中插單引號**</font>|`"A Bug's Life"`|`"A Bug's Life"`|同上。|
|<font color=red>**9**</font>|<font color=red>**前後綴雙引號,中置逗點`,`**</font>|`"男燒衣,女燒衣", 1975, 白駒榮,杜煥`|`['男燒衣,女燒衣', ' 1975', ' 白駒榮', '杜煥']`|<font color=red>`"男燒衣,女燒衣"`</font>視</br>為一欄。list只有4個元素。</br><font color=red>**注意:**</font>原字串中的</br><font color=#8B4513>**`1975`**</font>、<font color=#8B4513>**`白駒榮`**</font>分別</br>和其前面的<font color=#8B4513>`,`</font>間有</br>一空白,轉出來的</br>list元素隨之前置</br>空白。而原<font color=#8B4513>**`杜煥`**</font>前</br>面並無空格,list</font></br>亦未留白。|
|<font color=red>**10**</font>|<font color=red>**前後綴單引號,中置逗點**</font>|`'男燒衣,女燒衣', 1975, 白駒榮,杜煥`|`["'男燒衣", " 女燒衣'", ' 1975', ' 白駒榮', '杜煥']`</font>|<font color=blue>`'男燒衣,女燒衣'`</font>視</br>為兩欄。list共有5個元素。|
|11|欄首一段包雙引號|`"萬惡"淫為首`|`'萬惡淫為首'`|雙引號刪除。|
|12|欄中一段包雙引號|`萬惡"淫"為首`|`'萬惡"淫"為首'`|雙引號保留。|
|13|欄末一段包雙引號|`萬惡淫為"首"`|`'萬惡淫為"首"'`|雙引號保留。|
|14|首末各一段包雙</br>引號|`"萬惡"淫為"首"`|`'萬惡淫為"首"'`|欄首雙引號去除,欄末保留。|
|15|欄首一段包單引號|`'萬惡'淫為首`|`"'萬惡'淫為首"`|單引號保留。|
|16|欄中一段包單引號|`萬惡'淫'為首`|`"萬惡'淫'為首"'`|單引號保留。|
|17|欄末一段包單引號|`萬惡淫為'首'`|`"萬惡淫為'首'"`|單引號保留。|
|18|首末各一段包單</br>引號|`'萬惡'淫為'首'`|`"'萬惡'淫為'首'"`|單引號全保留。|
|19|前單引號,後雙</br>引號|`'雷鳴金鼓戰笳聲"`|`'\'雷鳴金鼓戰笳聲"'`|單雙引號均保留。單引號以`\`跳脫(escape)。 |
|20|欄中分插雙單引號|`大鬧"廣昌'隆`|`'大鬧"廣昌\'隆'`|同上。|
|<font color=red>**21**</font>|<font color=red>**前綴雙引號,但</br>無相對應的後綴</br>雙引號**</font>|`"重慶森林,1994,王家衛` <font color=#009E60>↩️</font> `悲情城市,1989, 侯孝賢` <font color=#009E60>↩️</font> `霸王別姬,1993, 陳凱歌`|`[['重慶森林,1994,王家衛\n悲情城市,1989, 侯孝賢\n霸王別姬,1993, 陳凱歌']]`|系統誤判。原資料3筆各3欄,list誤作1筆1欄。|
|<font color=red>**22**</font>|<font color=red>**中插單一雙引號**</font>|`重慶"森林,1994,王家衛` <font color=#009E60>↩️</font> `悲情城市,1989, 侯孝賢` <font color=#009E60>↩️</font> `霸王別姬,1993, 陳凱歌`|`[['重慶"森林', '1994', '王家衛'], ['悲情城市', '1989', ' 侯孝賢'], ['霸王別姬', '1993', ' 陳凱歌']]`|list正確列出</br>3筆3欄,無誤判。</br>雙引號保留。|
## csv檔案寫入
```python=
# -*- 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模組寫檔,同樣享受操作方便和資料正確的好處。以上程式輸出:
![](https://i.imgur.com/DHztQsn.png =300x)
用Excel開啟。正確無誤:
![](https://i.imgur.com/ZsW5D9N.png =300x)
假設以普通文字檔方式寫檔,程式碼大概會是:
```python=
# -*- 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卻長這個模樣:
![](https://i.imgur.com/wJEhRGD.png =300x)
用Excel開啟:
![](https://i.imgur.com/9dX7Qix.png =300x)
資料滲沙挾石,雜草叢生。
一言蔽之,檔案寫入也請採用csv模組,別重新發明輪子。
---
另外,寫檔須注意一點:`open()`方法要加上`newline=''`參數。
```python=
# 下面的open()方法未加newline=''參數。
csvFileToWrite = open(r'd:\python\member1.csv', 'w', encoding='utf-8')
```
以上程式碼未加這參數,後果是:產生的csv檔在換列處會多製造一個carriage return符號,即16進位的0D。本來Windows文字檔換列碼是16進位的0D 0A,多一個c/r,變成<font color=red>0D</font> 0D 0A。以Windows的notepad「記事本」瀏覽,看似正常。用notepad++開啟,則看到兩列間插入一空列:
![](https://i.imgur.com/gv1Okhw.png =300x)
Excel開檔亦如此:
![](https://i.imgur.com/4DVaIRy.png =300x)
所以,欲掃除文盲,不,是掃除空列,寫檔毋忘`newline=''`,除非真想製造空列效果。
## 結論
建議無論是讀是寫,都恭請standard library的csv模組大爺出馬,勿土法練鋼拿一般文字檔I/O勉強湊合。文字檔I/O對象是普通文件。對csv這樣有格式要求的檔案,引進專屬模組才事半功倍。
>[<font color=#5E86C1>「工欲善其事,必先利其器」。</font>](http://img.taopic.com/uploads/allimg/110930/114-11093014455299.jpg "工具不嫌多,越多越順手。")
>[color=#5E86C1]
---
外部參考資料:
[如何使用Excel開啟UTF-8格式的CSV檔案](http://crm2.tw/salesforce-tips/excel-data-process-utf-8/ "本篇介紹如何使用Excel儲存及開啟UTF-8格式的CSV檔案。")
###### tags: `csv` `文字檔` `讀檔` `寫檔` `standard library` `csv模組` `newline`