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.writer
と csv.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 でも大丈夫だと思う。
(しかし、このオプション。相当古くからあるのね。。。ちょっとショックだった。。。)