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

原始碼： Lib/csv.py

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

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

`csv` 模組的 `reader` 及 `writer` 物件可以讀取及寫入序列。程式設計師也可以透過 `DictReader` 及 `DictWriter` 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` 有被指定（在這個情況之下，沒有引號的欄位都會被轉換成浮點數），否則不會進行自動資料型別轉換。

  一個簡短的用法範例：

      >>> import csv
      >>> with open('eggs.csv', newline='') as csvfile:
      ...     spamreader = csv.reader(csvfile, delimiter=' ', quotechar='|')
      ...     for row in spamreader:
      ...         print(', '.join(row))
      Spam, Spam, Spam, Spam, Spam, Baked Beans
      Spam, 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)。

  一個簡短的用法範例：

      import csv
      with open('eggs.csv', 'w', newline='') as csvfile:
          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]])

  將 dialect 與 name 進行關聯 (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` 模組定義了下列的類別：

class csv.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`。

  一個簡短的用法範例：

      >>> import csv
      >>> with open('names.csv', newline='') as csvfile:
      ...     reader = csv.DictReader(csvfile)
      ...     for row in reader:
      ...         print(row['first_name'], row['last_name'])
      ...
      Eric Idle
      John Cleese

      >>> print(row)
      {'first_name': 'John', 'last_name': 'Cleese'}

class csv.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`。

  一個簡短的用法範例：

      import csv

      with open('names.csv', 'w', newline='') as csvfile:
          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'})

class csv.Dialect

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

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

      import csv

      with open('students.csv', 'w', newline='') as csvfile:
          writer = csv.writer(csvfile, dialect='unix')

class csv.excel

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

class csv.excel_tab

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

class csv.unix_dialect

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

  在 3.2 版被加入.

class csv.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` 的使用範例：

    with open('example.csv', newline='') as csvfile:
        dialect = csv.Sniffer().sniff(csvfile.read(1024))
        csvfile.seek(0)
        reader = csv.reader(csvfile, dialect)
        # ... process CSV file contents here ...

`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` 模組定義下列例外：

exception csv.Error

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

## Dialect 與格式參數

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

Dialect 支援下列屬性：

Dialect.delimiter

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

Dialect.doublequote

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

  在輸出時，若 doublequote 是 `False` 且逸出字元沒有被設定，當一個引號在欄位中被發現時，`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

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

  在 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 檔案範例：

    import csv
    with open('some.csv', newline='') as f:
        reader = csv.reader(f)
        for row in reader:
            print(row)

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

    import csv
    with open('passwd', newline='') as f:
        reader = csv.reader(f, delimiter=':', quoting=csv.QUOTE_NONE)
        for row in reader:
            print(row)

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

    import csv
    with open('some.csv', 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerows(someiterable)

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

    import csv
    with open('some.csv', newline='', encoding='utf-8') as f:
        reader = csv.reader(f)
        for row in reader:
            print(row)

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

註冊一個新的 dialect ：

    import csv
    csv.register_dialect('unixpwd', delimiter=':', quoting=csv.QUOTE_NONE)
    with open('passwd', newline='') as f:
        reader = csv.reader(f, 'unixpwd')

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

    import csv, sys
    filename = 'some.csv'
    with open(filename, newline='') as f:
        reader = csv.reader(f)
        try:
            for row in reader:
                print(row)
        except csv.Error as e:
            sys.exit('file {}, line {}: {}'.format(filename, reader.line_num, e))

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

    import csv
    for row in csv.reader(['one,two,three']):
        print(row)

註解

[1] (1,2)

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