nikkie-ftnextの日記

イベントレポートや読書メモを発信

私、csv.readerのこと、何も分かってなかったんだな... 〜Dialectなるものを完全理解〜

はじめに

デカリボパトラッシュ、お誕生日おめでとうございます! nikkieです1

Python標準ライブラリのcsvモジュールとの付き合いが長いのですが、このたびreader(やwriter)のdialect引数の意味をようやく完全理解しました!
ここにアウトプットします。

目次

その出会いは入門者向けハンズオン

csvモジュールには、PyNyumonという入門者向けハンズオン2で出会いました。
pynyumon/2_scraping.md at 9d3a9cfc33b78043ea79bd00d4507eb1abea7402 · pynyumon/pynyumon · GitHub

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

CSVファイルを使うときは、だいたいこのコードをベースにします3
何回も書いているので手が覚えています(親の顔を見るより書いたコードなんじゃないかな)。

それほどまでに慣れたコードではあるのですが、csv.reader(f)という部分が何をやっているかは、昨日までの私は全く分かっていなかったのです。

csvモジュールのドキュメント再訪

csv.reader

https://docs.python.org/ja/3/library/csv.html#csv.reader

改めて引数を確認すると、csv.reader(csvfile, dialect='excel', **fmtparams)となっています。

カンマ区切り(CSV形式)のファイルを読み込むときはcsvfile引数のみを指定しますね。
タブ区切り(TSV形式)のファイルを読み込むときはdelimiter="\t"区切り文字も指定します。
これは、可変長キーワード引数fmtparamsを利用しているということです。

別のオプションである fmtparams キーワード引数は、現在の表現形式における個々の書式パラメータを上書きするために与えることができます。

引用した中に「上書き」とあります。
うわがき?
そう、書式化パラメタのグループが指定されていて、その一部を上書きしているんです(この理解が今回の一番の収穫です)。

書式化パラメタのグループを指定しているのが、dialect引数!

オプションとして dialect パラメータを与えることができ、特定の CSV 表現形式 (dialect) 特有のパラメータの集合を定義するために使われます。

詳細が書かれた節「Dialect クラスと書式化パラメータ」を見ていきましょう。

Dialect クラスと書式化パラメータ

レコードに対する入出力形式の指定をより簡単にするために、特定の書式化パラメータは表現形式 (dialect) にまとめてグループ化されます。

csv.Dialectというクラスがあるんです!
https://docs.python.org/ja/3/library/csv.html#csv.Dialect

https://github.com/python/cpython/blob/v3.11.3/Lib/csv.py#L23-L52

class Dialect:
    # 一部抜粋
    delimiter = None
    quotechar = None
    escapechar = None
    doublequote = None
    skipinitialspace = None
    lineterminator = None
    quoting = None

    def __init__(self):
        # 省略
    
    def _validate(self):
        # 省略

属性の意味は「Dialect クラスと書式化パラメータ」節で解説されます。
例えばdelimiter4は区切り文字ですね。
https://docs.python.org/ja/3/library/csv.html#csv.Dialect.delimiter

フィールド間を分割するのに用いられる 1 文字からなる文字列です。デフォルトでは ',' です。

csv.readerdialect引数のデフォルト値は'excel'という文字列でした。
これはcsv.excelクラスを指定していることになります。

https://docs.python.org/ja/3/library/csv.html#csv.excel

excel クラスは Excel で生成される CSV ファイルの通常のプロパティを定義します。これは 'excel' という名前の dialect として登録されています。

具体的な設定値を見たいので、ソースコードも覗きます。
https://github.com/python/cpython/blob/v3.11.3/Lib/csv.py#L54-L62

class excel(Dialect):
    delimiter = ','
    quotechar = '"'
    doublequote = True
    skipinitialspace = False
    lineterminator = '\r\n'
    quoting = QUOTE_MINIMAL

Dialectの各属性が設定されていますね。
ここにない属性はベースクラスのデフォルト値が使われるわけですね。
これらの属性には不変式があり、_validateメソッドを備えたクラスとして実装されているんだろうな〜という理解です5

以下の気付きがありました。

  • CSVファイルを読み込むときはcsv.excelというDialectの指定で十分なので、ほかの引数は指定しない
  • TSVファイルを読み込むときはcsv.excelの中のdelimiterだけ"\t"に上書きするために指定していた

TSVファイル用のDialectの存在を知る

ドキュメントを眺める中で、csv.excelのTSV版を知りました。
その名もcsv.excel_tab
https://docs.python.org/ja/3/library/csv.html#csv.excel_tab

excel_tab クラスは Excel で生成されるタブ分割ファイルの通常のプロパティを定義します。これは 'excel-tab' という名前の dialect として登録されています。

実装を見てみると、たしかにdelimiter以外はcsv.excelと共通です!
https://github.com/python/cpython/blob/v3.11.3/Lib/csv.py#L64-L67

class excel_tab(excel):
    delimiter = '\t'

これまでTSVファイルを読み込むときは、以下のように書いてきました。

with open("some.tsv", encoding="utf8", newline="") as f:
    reader = csv.reader(f, delimiter="\t")
    # readerを処理する

これはdialect引数を指定するだけでも書けるわけですね。

with open("some.tsv", encoding="utf8", newline="") as f:
    reader = csv.reader(f, dialect="excel-tab")
    # readerを処理する

タイプ量が短くなるわけではないですが(むしろ増えている)、csvモジュールに用意されていたわけだから、今後はこちらの書き方にしてみようかな

終わりに

csv.reader(f)が何をやっているか理解したことをアウトプットしました。

  • 書式化パラメタのグループ:Dialect
    • csv.excelというDialectがデフォルトで指定されている
  • 個別の書式化パラメタを上書きできる
    • TSVにはdelimiter="\t"と指定するが、これはDialectのdelimiter属性を上書きしている
  • TSV用のDialect csv.excel_tab が存在する

一度知ってしまうと、あまりに長い間、何も分かっていない状態で使い続けていたな〜と穴掘って埋まってしまいたくなります。
ただ今回知ることができたので、今後はcsvモジュールをこれまでよりも数段理解した状態で使えるのは楽しみですね。

今回紹介した個々の要素は『Python実践レシピ』の13.1でも取り上げられていました。
Python実践レシピ』はcsvモジュールのドキュメントの前段階としてよさそうです。
またドキュメントを当たる上で、このエントリはDialectにまつわる一つのストーリーを切り出した立ち位置となるかなと思います!

P.S. Dialectを自動で設定できる!

csv.Snifferというものの存在も知りました。
https://docs.python.org/ja/3/library/csv.html#csv.Sniffer

Sniffer クラスは CSV ファイルの書式を推理するために用いられるクラスです。

csv.Sniffer().sniffメソッドでdialectを推理させて、それをcsv.readerに渡すわけですね(利用例のコード参照)

ドキュメントのコードの解説が『Python実践レシピ』にありました(利用シーンも紹介されています)


  1. 優子先輩(部長)ももちろんおめでとうございます!
  2. 同種コレクションの辞書とも出会ったハンズオンです。 いったいいつから異種コレクションとしての辞書が僕の中で自然になってしまったのだろうか - nikkie-ftnextの日記
  3. 組み込み関数openの引数は2箇所変えます。newline引数とencoding引数も指定します。前者については https://docs.python.org/ja/3/library/csv.html#id3 をどうぞ
  4. delimiterは綴りを間違えることが多いのですが、delimit(境界を定める)という動詞があることを知りました
  5. 不変式については 読書ログ | 『ロバストPython』10章「クラス」、不変式を維持せよ!と声高に叫ぶ章(議論する前の理解のまとめ) - nikkie-ftnextの日記 をどうぞ