Windows で Python の csv モジュールで改行コードを正しく出力する

Python 標準の csv パッケージ は CSV ファイルを手軽に入出力できるので重宝しているのだけれど、Windows で使うと改行コードがおかしくなることがあった。見た目的には「各行が 2 回改行されてしまう」ような状態。これを回避する簡単な方法がやっと分かったので、今日はその備忘録。

普通に CSV 書き出しを実装すると、テキストモードで開いたファイルオブジェクトを渡して csv.writer または csv.DictWriter を作り、その writerow() などのメソッドを呼ぶことになると思う。こうすると、残念なことに「CR+LF (\r\n) で出力されるべきところ CR+CR+LF (\r\n\n) が出力され」てしまう。多くの Windows プログラマはピンと来るだろうけれど、どうやら csv モジュールは OS を区別せず常に改行コードとして CR+LF (\r\n) を書き込んでしまっているのだと推測される。では書き込み先のファイルをバイナリモードで開けば良いかというと、Python ではバイナリモードで開いたファイルに str オブジェクトを直接書き込めないので UTF-8 にデコードしたりと面倒なことをしなくてはならなくなり…まあ、微妙だ。が、実は csv.writercsv.DictWriter のコンストラクタには lineterminator という名前付き引数を指定することができ、ここに "\n" を指定してやれば改行コードとして LF を使ってくれる。

実際にコード例を書いてみよう。次のようなスクリプトがあるとして:

# writecsv.py
import csv

rows = [["foo", "bar"], ["baz", "qux"]]

with open("bad.csv", "wt") as f:
    writer = csv.writer(f)
    for row in rows:
        writer.writerow(row)

with open("good.csv", "wt") as f:
    writer = csv.writer(f, lineterminator="\n")
    for row in rows:
        writer.writerow(row)

Windows で実行すると:

C:\Users\sgryjp\src\temp>python writecsv.py

C:\Users\sgryjp\src\temp>python -c "print(open('bad.csv', 'rb').read())"
b'foo,bar\r\r\nbaz,qux\r\r\n'

C:\Users\sgryjp\src\temp>python -c "print(open('good.csv', 'rb').read())"
b'foo,bar\r\nbaz,qux\r\n'

Linux (WSL) で実行すると:

sgryjp@anvil:/mnt/c/Users/sgryjp/src/temp$ python3 writecsv.py
sgryjp@anvil:/mnt/c/Users/sgryjp/src/temp$ python3 -c "print(open('bad.csv', 'rb').read())"
b'foo,bar\r\nbaz,qux\r\n'
sgryjp@anvil:/mnt/c/Users/sgryjp/src/temp$ python3 -c "print(open('good.csv', 'rb').read())"
b'foo,bar\nbaz,qux\n'

このように、lineterminator="\n" で出力した 2.csv の方では OS ネイティブの改行コードが使われている。どうしても RFC 4180 に準拠しなければならない等の事情が無ければ、この書き方をしておけば Windows でも Linux でも大丈夫だと思う。

(しかし、このオプション。相当古くからあるのね。。。ちょっとショックだった。。。)