はじめに
私的最強概念 こころおねえちゃん 🤗🤗🤯🤯🤯
nikkieです。
時として厄介な形式のCSVファイルを扱わなければならないことってありますよね。
csv
モジュールを使って挑んでいったのですが、読み込んだCSVでは、ダブルクォートが閉じるまでは区切り文字で分割されないという動きに翻弄されました。
この動きを理解するまでをアウトプットします。
目次
- はじめに
- 目次
- 復習:Dialectと書式化パラメタ
- 標準ライブラリcsvのテスト内容をサンプルとして理解していく
- CSV中のダブルクォートはquotecharなるものだった!
- quotecharは変えられる
- ダブルクォートを含んでも区切り文字で分割するには
- 終わりに
復習:Dialectと書式化パラメタ
csv.reader
はDialect(書式化パラメタのグループ)を指定していることを学びました。
csv.reader
のデフォルトのDialectはcsv.excel
です。
https://docs.python.org/ja/3/library/csv.html#csv.excel
CSVではなくTSVを読み込むときは、csv.reader
にdelimiter="\t"
と指定し、書式化パラメタを上書きします1。
標準ライブラリcsvのテスト内容をサンプルとして理解していく
csvモジュールのテストコードからCSV中のダブルクォートの理解が深まりました。
https://github.com/python/cpython/blob/v3.11.3/Lib/test/test_csv.py#L349-L363
テストコードではcsv.reader
をデフォルトのcsv.excel
Dialectで使い、必要に応じてfmtparams
を使ってパラメタを上書きします2。
csv.excel
Dialectでは、delimiterがカンマ(","
)です。
https://github.com/python/cpython/blob/v3.11.3/Lib/csv.py#L54-L56
以降の対話例は Python 3.10.9 で動作確認しています。
CSV中のダブルクォートはquotecharなるものだった!
ダブルクォートを含んだ行('1,",3,",5'
)を考えます。
https://github.com/python/cpython/blob/v3.11.3/Lib/test/test_csv.py#L350
>>> import csv >>> reader = csv.reader(['1,",3,",5']) >>> list(reader) [['1', ',3,', '5']]
この行は(カンマを4つ含んでいますが)3つに分割されました。
1の後のカンマ(delimiter)と、5の前のカンマ(delimiter)の2箇所で分割されています。
3の前後のカンマ(delimiter)では分割されていません。
この挙動の理解の鍵は、Dialectのquotechar
です。
https://docs.python.org/ja/3/library/csv.html#csv.Dialect.quotechar
delimiter や quotechar といった特殊文字を含むか、改行文字を含むフィールドをクオートする際に用いられる 1 文字からなる文字列です。デフォルトでは '"' です。
csv.excel
Dialectのquotechar
属性の値はダブルクォート('"'
)です。
https://github.com/python/cpython/blob/v3.11.3/Lib/csv.py#L57
動きとしては、CSVの行でquotechar
以降のdelimiter
は(quotechar
が閉じるまでは)delimiter
として扱われないわけですね。
これで',3,'
とdelimiterを含んだ部分ができた動きを説明できます。
quotechar
が閉じない場合は、quotechar
以降のdelimiter
では分割されません。
quotechar
以降が一切分割されないことになります。
>>> reader = csv.reader(['1,",3,,5']) >>> list(reader) [['1', ',3,,5']]
quotecharは変えられる
quotecharは書式化パラメタなので、delimiter同様、別の値(文字列)に変えられます。
ダブルクォートから@
(アットマーク)に変える例です。
ref: https://github.com/python/cpython/blob/v3.11.3/Lib/test/test_csv.py#L362
>>> reader = csv.reader(['1,",@,3,@,5'], quotechar="@") >>> list(reader) [['1', '"', ',3,', '5']]
ダブルクォートは今回はquotecharではないので、続くカンマ(delimiter)で分割されました。
@
(quotechar)以降のカンマ(delimiter)はdelimiterとして扱われないため、',3,'
という分割になっていますね。
ダブルクォートを含んでも区切り文字で分割するには
ダブルクォートを含んだ行('1,",3,",5'
)がすべてのカンマ(delimiter)で分割される方法をテストコードを参考に探します。
この行であれば 1 / " / 3 / " / 5 と5つに分かれてほしいです。
1️⃣ quotecharにNone
を指定する
quotecharにNone
を指定すると、すべてのdelimiterで分割されます。
https://github.com/python/cpython/blob/v3.11.3/Lib/test/test_csv.py#L351-L352
>>> reader = csv.reader(['1,",3,",5'], quotechar=None) >>> list(reader) [['1', '"', '3', '"', '5']]
CSV中にquotecharがないという指定と認識しています。
2️⃣ 代わりにquotingで指定する
テストコードを見ると別のやり方に気づきました。
quotingという引数も指定できるようです。
https://github.com/python/cpython/blob/v3.11.3/Lib/test/test_csv.py#L353-L354
>>> reader = csv.reader(['1,",3,",5'], quoting=csv.QUOTE_NONE) >>> list(reader) [['1', '"', '3', '"', '5']]
quotingも書式化パラメタの1つです。
https://docs.python.org/ja/3/library/csv.html#csv.Dialect.quoting
クオートがいつ writer によって生成されるか、また reader によって認識されるかを制御します。
csv.excel
Dialectでは、QUOTE_MINIMAL
に指定されています。
https://github.com/python/cpython/blob/v3.11.3/Lib/csv.py#L61
これはcsvモジュールに定義された定数の1つです。
https://docs.python.org/ja/3/library/csv.html#csv.QUOTE_MINIMAL
writerに対して指示すると説明されていますね。
(readerに対しては指示はないのでしょうか?)
この値を別の定数QUOTE_NONE
に変えます。
https://docs.python.org/ja/3/library/csv.html#csv.QUOTE_NONE
reader に対しては、クオート文字の特別扱いをしないように指示します。
ダブルクォートを(quotecharとして)特別扱いしないことで、すべてのカンマ(delimiter)で分割されたわけですね!
終わりに
csv.readerでCSVの中のダブルクォートがどう扱われるか、少し深まった理解をアウトプットしました。
- csv.readerはデフォルトで 、CSVの中のダブルクォートをquotecharとして扱う
- CSVファイルの行をパースするとき、quotecharが再度quotecharで閉じるまで、その中に含まれるdelimiterでは分割されない
- 分割されるように動きを変えるには2つのやり方がある
- quotecharに
None
を指定する - quotingに
csv.QUOTE_NONE
を指定する
- quotecharに
CSVにはRFCがあるということだけ知っています。
この動きはRFCに沿っているんじゃないかと思うので、一度RFCを確認するともっと自信が持てるかもな〜と思いました。
-
Dialectを
"excel-tab"
に変えてもよいです↩ -
詳細はテストのヘルパーメソッド
_read_test
をどうぞ! https://github.com/python/cpython/blob/v3.11.3/Lib/test/test_csv.py#L292-L295↩