csv --- CSV 檔案讀取及寫入

原始碼:Lib/csv.py


所謂的 CSV (Comma Separated Values) 檔案格式是試算表及資料庫中最常見的匯入、匯出檔案格式。在嘗試以RFC 4180 中的標準化方式來描述格式之前,CSV 格式已經使用了許多年。由於缺少一個完善定義的標準,意味著各個不同的應用程式會在資料產生及銷毀時有微妙的差別。這些不同之處使得從不同資料來源處理 CSV 檔案時會非常擾人。儘管如此,雖然分隔符號和引號字元有所不同,整體的格式非常相似,可以寫個單一模組來高效率的操作這樣的資料,讓程式設計師可以隱藏讀取及寫入資料的細節。

csv 模組實作透過 class 去讀取、寫入 CSV 格式的表格資料。它讓程式設計師可以說出:「以 Excel 為首選並寫入該種格式的資料」或是「從 Excel 產生的檔案來讀取資料」,且無需知道這是 Excel 所使用的 CSV 格式等精確的細節。程式設計師也可以描述其他應用程式所理解的 CSV 格式或他們自行定義具有特殊意義的 CSV 格式。

csv 模組的readerwriter 物件可以讀取及寫入序列。程式設計師也可以透過DictReaderDictWriter class(類別)使用 dictionary (字典)讀取及寫入資料。

也參考

PEP 305 - CSV 檔案 API

Python Enhancement Proposal (PEP) 所提出的 Python 附加功能。

模組內容

csv 模組定義了以下函式:

csv.reader(csvfile,dialect='excel',**fmtparams)

回傳一個讀取器物件 (reader object) 並處理在指定的csvfile 中的每一行,csvfile 必須是字串的可疊代物件 (iterable of strings),其中每個字串都要是讀取器所定義的 csv 格式,csvfile 通常是個類檔案物件或者 list。如果csvfile 是個檔案物件,則需開啟時使用newline=''[1]dialect 為一個可選填的參數,可以用為特定的 CSV dialect(方言) 定義一組參數。它可能為Dialect 的一個子類別 (subclass) 的實例或是由list_dialects() 函式回傳的多個字串中的其中之一。另一個可選填的關鍵字引數fmtparams 可以在這個 dialect 中覆寫 (override) 個別的格式化參數 (formatting parameter)。關於 dialect 及格式化參數的完整說明,請見段落Dialect 與格式參數

從 CSV 檔案讀取的每一列會回傳為一個字串列表。除非格式選項QUOTE_NONNUMERIC 有被指定(在這個情況之下,沒有引號的欄位都會被轉換成浮點數),否則不會進行自動資料型別轉換。

一個簡短的用法範例:

>>>importcsv>>>withopen('eggs.csv',newline='')ascsvfile:...spamreader=csv.reader(csvfile,delimiter=' ',quotechar='|')...forrowinspamreader:...print(', '.join(row))Spam, Spam, Spam, Spam, Spam, Baked BeansSpam, Lovely Spam, Wonderful Spam
csv.writer(csvfile,dialect='excel',**fmtparams)

回傳一個寫入器物件 (writer object),其負責在給定的類檔案物件 (file-like object) 上將使用者的資料轉換為分隔字串 (delimited string)。csvfile 可以為具有write() method 的任何物件。若csvfile 為一個檔案物件,它應該使用newline='' 開啟[1]dialect 為一個可選填的參數,可以用為特定的 CSV dialect 定義一組參數。它可能為Dialect 的一個子類別的實例或是由list_dialects() 函式回傳的多個字串中的其中之一。另一個可選填的關鍵字引數fmtparams 可以在這個 dialect 中覆寫個別的格式化參數。關於 dialect 及格式化參數的完整說明,請見段落Dialect 與格式參數。為了更容易與有實作 DB API 的模組互相接合,None 值會被寫成空字串。雖然這不是一個可逆的變換,這使得dump (傾印) SQL NULL 資料值到 CSV 檔案上就無需讓cursor.fetch* 呼叫回傳的資料進行預處理 (preprocessing)。其餘非字串的資料則會在寫入之前用str() 函式進行字串化 (stringify)。

一個簡短的用法範例:

importcsvwithopen('eggs.csv','w',newline='')ascsvfile:spamwriter=csv.writer(csvfile,delimiter=' ',quotechar='|',quoting=csv.QUOTE_MINIMAL)spamwriter.writerow(['Spam']*5+['Baked Beans'])spamwriter.writerow(['Spam','Lovely Spam','Wonderful Spam'])
csv.register_dialect(name[,dialect[,**fmtparams]])

dialectname 進行關聯 (associate)。name 必須為字串。這個 dialect 可以透過傳遞Dialect 的子類別進行指定;或是關鍵字引數fmtparams;或是以上兩者皆是,並透過關鍵字引數來覆寫 dialect 的參數。關於 dialect 及格式化參數的完整說明,請見段落Dialect 與格式參數

csv.unregister_dialect(name)

從 dialect 註冊表 (registry) 中,刪除與name 關聯的 dialect。若name 如果不是註冊的 dialect 名稱,則會產生一個Error

csv.get_dialect(name)

回傳一個與name 關聯的 dialect。若name 如果不是註冊的 dialect 名稱,則會產生一個Error。這個函式會回傳一個 immutable (不可變物件)Dialect

csv.list_dialects()

回傳所有已註冊的 dialect 名稱。

csv.field_size_limit([new_limit])

回傳目前的剖析器 (parser) 允許的最大字串大小。如果new_limit 被給定,則會變成新的最大字串大小。

csv 模組定義了下列的類別:

classcsv.DictReader(f,fieldnames=None,restkey=None,restval=None,dialect='excel',*args,**kwds)

建立一個物件,其運作上就像一般的讀取器,但可以將每一列資訊 map (對映) 到dict 中,可以透過選填的參數fieldnames 設定 key。

參數fieldnames 是一個sequence。如果fieldnames 被省略了,檔案f 中第一列的值會被當作欄位標題,且於結果中會被省略。如果fieldname 有提供,它們就會被使用,且第一列會被包含在結果中。不管欄位標題是如何決定的,dictionary 都會保留原始的排序。

如果一列資料中的欄位比欄位標題還多,其餘的資料及以restkey (預設為None)特指的欄位標題會放入列表當中並儲存。如果一個非空的 (non-blank) 列中的欄位比欄位標題還少,缺少的值則會填入restval (預設為None)的值。

所有其他選填的引數或關鍵字引數皆會傳遞至下層的reader 實例。

如果傳遞至fieldnames 的引數是個疊代器,則會被迫成為一個list

在 3.6 版的變更:回傳的列已成為型別OrderedDict

在 3.8 版的變更:回傳的列已成為型別dict

一個簡短的用法範例:

>>>importcsv>>>withopen('names.csv',newline='')ascsvfile:...reader=csv.DictReader(csvfile)...forrowinreader:...print(row['first_name'],row['last_name'])...Eric IdleJohn Cleese>>>print(row){'first_name': 'John', 'last_name': 'Cleese'}
classcsv.DictWriter(f,fieldnames,restval='',extrasaction='raise',dialect='excel',*args,**kwds)

建立一個物件,其運作上就像一般的寫入器,但可以將 dictionary map 到輸出的列上。參數fieldnames 是一個鍵值的sequence 且可以辨識 dictionary 中傳遞至writerow() method 寫入至檔案f 中的值。如果 dictionary 中缺少了fieldnames 的鍵值,則會寫入選填的參數restval 的值。如果傳遞至writerow() method 的 dictionary 包含了一個fieldnames 中不存在的鍵值,選填的參數extrasaction 可以指出該執行的動作。如果它被設定為'raise',預設會觸發ValueError。如果它被設定為'ignore',dictionary 中額外的值會被忽略。其他選填的引數或關鍵字引數皆會傳遞至下層的writer 實例。

請記得這不像類別DictReader,在類別DictWriter 中,參數fieldnames 並不是選填的。

如果傳遞至fieldnames 的引數是個疊代器,則會被迫成為一個list

一個簡短的用法範例:

importcsvwithopen('names.csv','w',newline='')ascsvfile:fieldnames=['first_name','last_name']writer=csv.DictWriter(csvfile,fieldnames=fieldnames)writer.writeheader()writer.writerow({'first_name':'Baked','last_name':'Beans'})writer.writerow({'first_name':'Lovely','last_name':'Spam'})writer.writerow({'first_name':'Wonderful','last_name':'Spam'})
classcsv.Dialect

類別Dialect 是一個容器類別,其屬性 (attribute) 包含如何處理雙引號、空白、分隔符號等資訊。由於缺少一個嚴謹的 CSV 技術規範,不同的應用程式會產出有巧妙不同的 CSV 資料。Dialect 實例定義了reader 以及writer 的實例該如何表示。

所有可用的Dialect 名稱會透過list_dialects() 回傳,且它們可以透過特定readerwriter 類別的初始器 (initializer,__init__) 函式進行註冊,就像這樣:

importcsvwithopen('students.csv','w',newline='')ascsvfile:writer=csv.writer(csvfile,dialect='unix')
classcsv.excel

類別excel 定義了透過 Excel 產生的 CSV 檔案的慣用屬性。它被註冊的 dialect 名稱為'excel'

classcsv.excel_tab

類別excel_tab 定義了透過 Excel 產生並以 Tab 作為分隔的 CSV 檔案的慣用屬性。它被註冊的 dialect 名稱為'excel-tab'

classcsv.unix_dialect

類別unix_dialect 定義了透過 UNIX 系統產生的 CSV 檔案的慣用屬性,換句話說,使用'\n' 作為換行符號且所有欄位都被引號包覆起來。它被註冊的 dialect 名稱為'unix'

在 3.2 版被加入.

classcsv.Sniffer

類別Sniffer 被用來推斷 CSV 檔案的格式。

類別Sniffer 提供了兩個 method:

sniff(sample,delimiters=None)

分析給定的sample 且回傳一個Dialect 子類別,反應出找到的格式參數。如果給定選填的參數delimiters,它會被解釋為一個字串且含有可能、有效的分隔字元。

has_header(sample)

如果第一列的文字顯示將作為一系列的欄位標題,會分析 sample 文字(假定他是 CSV 格式)並回傳True。檢查每一欄時,會考慮是否滿足兩個關鍵標準其中之一,判斷 sample 是否包含標題:

  • 第二列至第 n 列包含數字

  • 第二列到第 n 列包含的字串中至少有一個值的長度與該行的假定標題的長度不同。

對第一列之後的二十個列進行取樣;如果超過一半的行及列滿足條件,則返回True

備註

此方法是一個粗略的啟發,可能會產生偽陽性及偽陰性 (false positives and negatives)。

一個Sniffer 的使用範例:

withopen('example.csv',newline='')ascsvfile:dialect=csv.Sniffer().sniff(csvfile.read(1024))csvfile.seek(0)reader=csv.reader(csvfile,dialect)# ... 在這邊處理 CSV 檔案 ...

csv 模組定義了以下常數:

csv.QUOTE_ALL

引導writer 物件引用所有欄位。

csv.QUOTE_MINIMAL

引導writer 物件只引用包含特殊字元的欄位,例如:分隔符號引號、或是分行符號 的其他字元。

csv.QUOTE_NONNUMERIC

引導writer 物件引用所有非數字的欄位。

引導reader 物件轉換所有非引用的欄位為float

csv.QUOTE_NONE

引導writer 物件不得引用欄位。目前的分隔符號 出現在輸出資料時,在他之前的字元是目前的*逸出字元 (escape character)*。如果沒有設定*逸出字元*,若遇到任何字元需要逸出,寫入器則會引發Error

引導reader 物件不對引號進行特別處理。

csv.QUOTE_NOTNULL

引導writer 物件引用所有非None 的欄位。這與QUOTE_ALL 相似,除非如果欄位值為None,該欄位則被寫成空(沒有引號)字串。

引導reader 物件將空(沒有引號)欄位直譯 (interpret) 為None,否則會和QUOTE_ALL 有相同的表現方式。

在 3.12 版被加入.

csv.QUOTE_STRINGS

引導writer 物件永遠在字串的欄位前後放置引號。這與QUOTE_NONNUMERIC 相似,除非如果欄位值為None,該欄位則被寫成空(沒有引號)字串。

引導reader 物件將空(沒有引號)字串直譯為None,否則會和QUOTE_ALL 有相同的表現方式。

在 3.12 版被加入.

csv 模組定義下列例外:

exceptioncsv.Error

當偵測到錯誤時,任何函式都可以引發。

Dialect 與格式參數

為了讓指定輸入及輸出紀錄的格式更方便,特定的格式化參數會被組成 dialect。一個 dialect 是Dialect class 的子類別,其包含多個描述 CSV 檔案格式的多個屬性。當建立readerwriter 物件時,程式設計師可以指定一個字串或是一個Dialect 的子類別作為 dialect 參數。此外,或是作為替代,在dialect參數中,程式設計師可以指定個別的格式化參數,其與Dialect 類別定義的屬性具有相同的名字。

Dialect 支援下列屬性:

Dialect.delimiter

一個單一字元 (one-character) 的字串可已用來分割欄位。預設為','

Dialect.doublequote

控制quotechar 的實例何時出現在欄位之中,並讓它們自己被放在引號之內。當屬性為True,字元會是雙引號。若為False,在quotechar 之前會先使用escapechar 作為前綴字。預設為True

在輸出時,若doublequoteFalse逸出字元沒有被設定,當一個引號在欄位中被發現時,Error 會被引發。

Dialect.escapechar

一個會被寫入器使用的單一字元的字串,當quoting 設定為QUOTE_NONE 時逸出分隔符號;當doublequote 設定為False 時逸出引號。在讀取時,逸出字元會移除後面的字元以及任何特殊意義。預設為None,表示禁止逸出。

在 3.11 版的變更:escapechar 為空是不被接受的。

Dialect.lineterminator

writer 產生被用來分行的字串。預設為'\r\n'

備註

reader 是 hard-coded 辨別'\r' or'\n' 作為行尾 (end-of-line),並忽略分行符號。未來可能會改變這個行為。

Dialect.quotechar

一個單一字元的字串被用於引用包含特殊字元的欄位,像是delimiterquotechar 或是換行字元。預設為'"'

在 3.11 版的變更:quotechar 為空是不被允許的。

Dialect.quoting

控制 writer 何時產生引號,以及 reader 如何辨識引號。他可以使用任何QUOTE_* 常數且預設為QUOTE_MINIMAL

Dialect.skipinitialspace

若為True,在緊接著分隔符號後的空格會被忽略。預設為False

Dialect.strict

若為True,若有錯誤的 CSV 輸入則會引發Error。預設為False

讀取器物件

讀取器物件(reader() 函式回傳的DictReader 實例與物件)有下列公用方法 (public method):

csvreader.__next__()

回傳一個列表為讀入器的可疊代物件的下一列內容(若該物件是由reader() 回傳)或是一個 dict(若為DictReader 實例),會依據目前的Dialect 進行剖析。通常會用next(reader) 來進行呼叫。

讀取器物件有下列公用屬性 (public attributes):

csvreader.dialect

dialect 的唯讀敘述,會被剖析器使用。

csvreader.line_num

來源疊代器所讀取的行數。這與回傳的紀錄數不同,因為可以進行跨行紀錄。

DictReader 物件有下列公用屬性:

DictReader.fieldnames

若在建立物件時沒有作為參數傳遞,這個屬性會在第一次存取之前或是第一筆資料被讀取之前進行初始化 (initialize)。

寫入器物件

writer 物件(writer() 函式回傳的DictWriter 實例與物件)有下列公用方法。對於writer 物件而言,一個中必須為一個可疊代的字串或是數字;對於DictWriter 物件而言,則必須為一個 dictionary ,且可以對應欄位標題至字串或數字(會先透過str() 進行傳遞)。請注意,在寫入複數 (complex number) 時會用小括號 (parens) 包起來。這可能在其他程式讀取 CSV 檔案時導致某些問題(假設他們完全支援複雜數字)。

csvwriter.writerow(row)

將參數row 寫入至寫入器的檔案物件中,並依照目前的Dialect 進行格式化。回傳下層檔案物件write 方法的回傳值。

在 3.5 版的變更:新增對任意可疊代物件 (arbitrary iterables) 的支援。

csvwriter.writerows(rows)

rows 中所有元素(為上述的一個可疊代的row 物件)寫入至寫入器的檔案物件中,並依照目前的 dialect 進行格式化。

寫入器物件有下列公用屬性:

csvwriter.dialect

dialect 的唯讀敘述,會被寫入器使用。

DictWriter 物件有下列公用方法:

DictWriter.writeheader()

將具欄位標題的一列(於建構函式 (constructor) 中指定的)寫入至寫入器的檔案物件中,並依照目前的 dialect 進行格式化。回傳內部呼叫csvwriter.writerow() 的回傳值。

在 3.2 版被加入.

在 3.8 版的變更:writeheader() 現在也會回傳內部呼叫csvwriter.writerow() 的回傳值。

範例

最簡單的讀取 CSV 檔案範例:

importcsvwithopen('some.csv',newline='')asf:reader=csv.reader(f)forrowinreader:print(row)

讀取一個其他格式的檔案:

importcsvwithopen('passwd',newline='')asf:reader=csv.reader(f,delimiter=':',quoting=csv.QUOTE_NONE)forrowinreader:print(row)

相對最簡單、可行的寫入範例為:

importcsvwithopen('some.csv','w',newline='')asf:writer=csv.writer(f)writer.writerows(someiterable)

open() 被使用於開啟並讀取一個 CSV 檔案,該檔案會預設使用系統預設的編碼格式(請見locale.getencoding()),並解碼為 unicode。若要使用不同編碼格式進行檔案解碼,請使用 open 函式的encoding 引數:

importcsvwithopen('some.csv',newline='',encoding='utf-8')asf:reader=csv.reader(f)forrowinreader:print(row)

同理可以應用到使用不同編碼格式進行寫入:當開啟輸出檔案時,指定encoding 引數。

註冊一個新的 dialect :

importcsvcsv.register_dialect('unixpwd',delimiter=':',quoting=csv.QUOTE_NONE)withopen('passwd',newline='')asf:reader=csv.reader(f,'unixpwd')

稍微進階的讀取器用法 -- 擷取及回報錯誤:

importcsv,sysfilename='some.csv'withopen(filename,newline='')asf:reader=csv.reader(f)try:forrowinreader:print(row)exceptcsv.Errorase:sys.exit('file{}, line{}:{}'.format(filename,reader.line_num,e))

而當模組無法直接支援剖析字串時,仍可以輕鬆的解決:

importcsvforrowincsv.reader(['one,two,three']):print(row)

註解

[1](1,2)

如果newline='' 沒有被指定,則嵌入引號中的換行符號不會被正確直譯,使用\r\n 行尾 (linending) 的平台會寫入額外的\r。自從 csv 模組有自己 (統一的) 換行處理方式,因此指定newline='' 會永遠是安全的。