Pythonのopen組み込み関数では、「改行」に関して「優しい」仕様になっています。
しかし、私のような日曜プログラマは半年に1回ぐらいやっちまうのですが、稀にこの気遣い仕様を忘れて、ハマってしまうこともあるので、ここに備忘録として記しておきます。
まず、open 関数と、改行の振る舞いを制御するnewline引数については次のとおりです。
https://docs.python.org/ja/3/library/functions.html#open
※独り言:Pythonの公式サイトのリファレンスは、比較的ググラビリティが低い感じがする。
open組み込み関数のnewlineオプションの動作確認
公式リファレンスにしっかり書いてあるのですが、いかんせん忘れがちな挙動があります。
まずは、確認用スクリプト。
for nl in [None,'\r\n','\n','\r','']: lines = open('kaigyo.txt','r',encoding='utf-8',newline=nl).readlines() print('') print(f'newline={repr(nl)}') print(lines)
newline=None (newlineを指定しない場合。つまりデフォルト。)
改行っぽいやつを改行とみなして扱う。readlines()では、その環境の改行(os.linesep)に置き換えられたものが得られる。 (「ユニバーサル改行モード」と呼ぶらしい)
たまにこれの存在を忘れていて、荒れた改行コードのファイルを解析する時に落とし穴にハマっってしまう。
newline='\n' または '\r\n' または '\r'
指定のもののみ改行とみなして、readlines()でリストとして読み込む。特に改行の標準化などは行わない(元の入力のまま)。
newline=''
改行っぽいやつを改行とみなして扱う。readlines()では、元の入力のままの値が得られる。
IPythonでの確認
In [19]: ! echo -e "a,b,c\nd,e,f\r\ng,h,i\rj,kl" > kaigyo.txt In [20]: ! od -x kaigyo.txt ...: 0000000 2c61 2c62 0a63 2c64 2c65 0d66 670a 682c 0000020 692c 6a0d 6b2c 0a6c 0000030 In [21]: for nl in [None,'\r\n','\n','\r','']: ...: lines = open('kaigyo.txt','r',encoding='utf-8',newline=nl).readlines() ...: print(f'newline={repr(nl)}') ...: print(lines) ...: newline=None ['a,b,c\n', 'd,e,f\n', 'g,h,i\n', 'j,kl\n'] ※ \r\n \r単独 \n単独の全てが改行として扱われる。readlines()では、筆者の環境では「\n」に標準化。 newline='\r\n' ['a,b,c\nd,e,f\r\n', 'g,h,i\rj,kl\n'] ※ \r\nが改行扱い newline='\n' ['a,b,c\n', 'd,e,f\r\n', 'g,h,i\rj,kl\n'] ※\nが改行扱い newline='\r' ['a,b,c\nd,e,f\r', '\ng,h,i\r', 'j,kl\n'] ※\rが改行扱い newline='' ['a,b,c\n', 'd,e,f\r\n', 'g,h,i\r', 'j,kl\n'] ※改行っぽいやつは改行扱いされ、かつ標準化はしない。