Pythonでファイルを読み書きするとき、改行コードはどうなるの?

Pythonでテキストファイルを読み込んだとき、あるいはテキストファイルへ書き込むときに改行コードはどうなるんだろう?と思ったことはありませんか。

この記事では、Pythonでテキストファイルを読み込むとき、あるいはPythonからテキストファイルに書き込むときに、改行コードがどうなるか?ということについて仕組みから詳しく解説します。

目次

改行コードとは

本題に入る前に、改行コードについて簡単におさらいしておきましょう。プログラムを書く方はご存知だろうが、OSによってデフォルトで使用される改行コードは異なります。現在使われている改行コードには次の3つがあります。

LF(ラインフィード)

Line Feed(ラインフィード)と呼ばれる制御文字。Unix、Linux、Macで改行コードとして使われている。エスケープシーケンスで表すと「\n」。

CR(キャリッジリターン)

Carrige Return(キャリッジリターン)と呼ばれる制御文字。古いMacで改行コードとして使われていた。エスケープシーケンスで表すと「\r」。最近のMacではLFが使われているので、もはやこの改行コードに出会う機会は少ない。

CRLF

Windowsでは、CRの後にLFが続く2文字を改行コードとして扱う。エスケープシーケンスで表すと「\r\n」。

改行コードの違いを意識してプログラミングしないとトラブルになることがあります。例えばCRLFを期待しているプログラムが改行コードがLFのテキストファイルを読み込むと、CRLFが一つも見つからないのでテキスト全体を1行として読み込んだりします。

一方、LFのみの改行コードを期待しているプログラムがCRLFのテキストファイルを読み込むと、行に分割したとき(つまりLFで分割)に、行末にCR文字が残ってしまいます。Windowsで編集したファイルをLinuxなどに持っていく場合、よくこのミスで問題が起きます。

改行コードを制御するnewline引数

open()関数には改行コードを制御するnewline引数があります。ファイルの読み書き時に改行コードがどうなるかは、このnewline引数に渡す値によって決まります。

テキストファイルを読み込んだときの改行コード

open()関数でテキストファイルを開き、read()メソッドなどて読み込んだときの改行コードは、そのnewline引数で制御されます。

newline引数に渡せる値、およびその値を渡したときの動作を次の表にまとめます。newline引数を省略すると、デフォル値のNoneになります。

newline引数の値動作の説明
Noneuniversal newlinesモードが有効になり、ファイルで使われている改行コード(「\n」、「\r」、「\r\n」のいずれか)を自動的に認識する。認識された改行コードは呼び出し元へ返す前に「\n」に変換される。
”(空文字列)universal newlinesモードが有効になり、ファイルで使われている改行コード(「\n」、「\r」、「\r\n」のいずれか)を自動的に認識する。改行コードは変換されないで、そのまま呼び出し元へ返される。
‘\n’、’\r’、 ‘\r\n’newline引数に指定された文字列を改行コードと認識する。改行コードは変換されず、そのまま呼び出し元へ返される。

universal newlinesモードとは「\n」、「\r」、「\r\n」のいずれでも柔軟に改行コードとして認識するモードです。例えばCRLFを使うファイルを読み込むと「\r\n」が改行コードと認識され、 LFを使うファイルなら「\n」が改行コードと認識されます。

open()関数でnewline引数を指定する例をいくつか以下に示します。

open('textfile.txt', newline=None)
open('textfile.txt', newline='')
open('textfile.txt', newline='\n')

テキストファイルに書き込んだときの改行コード

読み込みとき同様に、ファイルへ書き込むときの改行コードもopen()関数のnewline引数で制御されます。

newline引数に渡せる値、およびその値を渡したときの動作を次の表にまとめます。newline引数を省略すると、デフォル値のNoneになります。

newline引数の値動作の説明
None書き出す文字列の「\n」は、システムデフォルトの改行コード(os.linesepの値)へ変換される。
” (空文字列)または ‘\n’書き出す文字列の改行コードは変更されず、そのまま書き出される。
‘\r’ または ‘\r\n’書き出す文字列の「\n」は、 newline 引数の値へ変換される。

osモジュールの os.linesep は、プログラムを実行しているOSで使われる改行コードが保存されています。Windowsでプログラムを実行していれば「\r\n」、LinuxやMacで実行していれば「\n」です。

open()関数でnewline引数を指定する例をいくつか以下に示します。

open('textfile.txt', 'w', newline=None)
open('textfile.txt', 'w', newline='')
open('textfile.txt', 'w', newline='\n')

newline引数による改行コードの制御を実際に確認する

ここからは実際にコードを打ち込んで、newline引数による改行コードの制御を実際に確認していきます。

前述の表の説明をコードで実演する内容なので、表の説明で理解できたならここは読み飛ばしても構いません。表の説明ではいまいち理解できなかった、あるいは興味がある方は引き続きお付き合いください。

テキストファイルを読み込むときの動作を説明してから、書き込み時の動作を説明します。

読み込み時の確認方法

最初に読み込み時の改行コードの確認方法を説明します。

まずは読み込み用のテキストファイルとして「crlf.txt」、「lf.txt」、「cr.txt」の3つのファイルを用意します。これらのファイルはそれぞれ、改行コードに「\r\n」、「\n」、「\r」を使います。

3つのファイルの内容を、catとodコマンドで表示した結果を以下に示します。

$ cat crlf.txt 
CRLF
CRLF
$ od -c crlf.txt 
0000000    C   R   L   F  \r  \n   C   R   L   F  \r  \n                
0000014
$ cat lf.txt 
LF
LF
$ od -c lf.txt 
0000000    L   F  \n   L   F  \n                                        
0000006
$ cat cr.txt 
$ od -c cr.txt 
0000000    C   R  \r   C   R  \r                                        
0000006

そしてテストを簡単にするために、これらのファイルは次のread_test()関数を定義して読み込みます。この関数に渡されたfname引数とnl引数は、それぞれopen()関数のファイル名とnewline引数に渡されます。

この関数はファイルの内容をreadlines()メソッドで読み込み、その結果を返します。readlines()メソッドは行を認識して、各行を要素とするリストを返しますので、どの改行コードを認識しているかわかるためです(ただし、わからない場合もあります。詳しくはテストした結果を見て貰えばわかるでしょう)。

>>> def read_test(fname, nl):
...     with open(fname, newline=nl) as f:
...         return f.readlines()
... 

読み込み時の動作テスト

では実際に、open()関数のnewline引数に渡す値によって、読み込んだ改行コードがどのような結果になるか、前述のテキストファイルとread_test()関数を使用し確認してみましょう。

newline引数に「None」を渡す

open()関数のnewline引数を省略、あるいは「None」を渡して呼び出すと、universal newlinesモードが有効になります。そしてuniversal newlinesモードで認識された改行コードは、read()などが呼び出し元に返す前に認識した改行コードを「\n」へ変換します。

実際にcrlf.txtを読み込んだ結果を以下に示します。

>>> read_test('crlf.txt', None)
['CRLF\n', 'CRLF\n']

universal newlinesモードが有効なので「\r\n」を改行コードと認識します。そしてその改行コードは「\n」へ変換されます。

lf.txtを読み込んだ結果も見てみましょう。

>>> read_test('lf.txt', None)
['LF\n', 'LF\n']

予想通りの結果になっていますでしょうか。これは例では改行コードを「\n」と認識し、「\n」に変換しているはずです。実際、そのような結果になっています。

cr.txtを読み込んだ結果は次のとおりです。

>>> read_test('cr.txt', None)
['CR\n', 'CR\n']

つまりopen()関数のnewline引数を省略したとき(あるいはNoneを渡したとき)は、ファイルでどの改行コードを使っていようが、Pythonで読み込んだ後は常に「\n」になっているということです。

newline引数に「”(空文字列)」を渡す

newline引数に「”(空文字列)」を渡すとuniversal newlinesモードが有効になります。しかし、認識した改行コードが変換されることはありません。

次の実行例を見てみましょう。

>>> read_test('crlf.txt', '')
['CRLF\r\n', 'CRLF\r\n']
>>> read_test('lf.txt', '')
['LF\n', 'LF\n']
>>> read_test('cr.txt', '')
['CR\r', 'CR\r']

Pythonで読み込んだ後も改行コードが変わっていないことはわかるでしょう。ただし、このテストでは改行コードが正しく認識されいることはまでは厳密には分かりません。なぜならcrlf.txtを読み込んだときに、改行コードを「\n」と認識していたとしても結果は変わらないからです。

newline引数に「\n」、「\r」、「\r\n」を渡す

open()関数のnewline引数に「\n」、「\r」、「\r\n」を渡すと、渡した値がファイルの改行コードと認識されます。認識された改行コードは変換されずにそのままread()関数などから返されます。

ここではcrlf.txtに対して、引数の値による違いを見てみます。

>>> read_test('crlf.txt', '\r\n')
['CRLF\r\n', 'CRLF\r\n']
>>> read_test('crlf.txt', '\n')
['CRLF\r\n', 'CRLF\r\n']
>>> read_test('crlf.txt', '\r')
['CRLF\r', '\nCRLF\r', '\n']

3つ目の「\r」を渡した結果に注目してください。この場合、Pythonは改行コードを「\r」と認識しているので「\r」までを行と認識します。そのため上記のような出力結果になります。「\n」を渡したときは、もちろん改行コードは「\n」と認識されていまが、これ例の出力結果からは、そうであるかは判別できません。

また、読み込んだ改行コードが変換されていないことについても確認してください。

lf.txtを読み込んだ例を以下に示します。

>>> read_test('lf.txt', '\r\n')
['LF\nLF\n']
>>> read_test('lf.txt', '\n')
['LF\n', 'LF\n']
>>> read_test('lf.txt', '\r')
['LF\nLF\n']

「\n」を渡したときだけ、2行で読み込まれています。それ以外の場合は、改行コードが見つからないので1行になっています。

書き込み時の確認方法

テキストファイへの書き込みには、次のwrite_test()関数を定義して使います。この関数にはnewline引数へ渡す値と、書き込むテキストを渡します。

>>> def write_test(nl, text):
...     with open('write_test', 'w', newline=nl) as f:
...         f.write(text)
... 

書き込むテキストには「’A\r\nB\r\n’」を使います。

>>> text = 'A\r\nB\r\n'

書き込んだテキストファイル(write_test)の改行コードは、引き続きodコマンドを使って確認します。

書き込み時の動作テスト

それでは確認用のプログラムを使ってファイルへ出力された改行コードがどうなるかみていきましょう。

newline引数に「None」を渡す。

open()関数のnewline引数を省略、あるいは「None」を渡すと、「\n」はos.linesep に置き換えられて出力されます。

まずはMacで実行したものを確認してみましょう。Macで実行すると os.linesep は「\n」になるので、「\n」が「\n」に置き換えられるので、テキストの内容は変わらずファイルへ書き込まれます。

>>> import os
>>> os.linesep
'\n'
>>> write_test(None, text)

$ od -c write_test 
0000000    A  \r  \n   B  \r  \n                                        
0000006

次にWindowsで実行してみます。Windowsで実行すると os.linesep は「\r\n」になるので、「\n」が「\r\n」に変換されてファイルへ書き込まれます。

>>> import os
>>> os.linesep
'\r\n'
>>> write_test(None, text)

$ od -c write_test 
0000000    A  \r  \r  \n   B  \r  \r  \n                                
0000010

このような変な結果にならないように、Pythonプログラムの中では改行コードに常に「\n」を使うことをお勧めします。

newline引数に「”」または「\n」を渡す。

newline引数に「”」か「\n」を渡すと、何も変換は行われず、テキストはそのままファイルへ書き込まれます。

>>> write_test('', text)

$ od -c write_test 
0000000    A  \r  \n   B  \r  \n                                        
0000006

>>> write_test('\n', text)

$ od -c write_test 
0000000    A  \r  \n   B  \r  \n                                        
0000006

newline引数に「\r」または「\r\n」を渡す。

newline引数に「\r」または「\r\n」を渡すと、テキストの「\n」は引数に渡された値に変換されてファイルへ書き込まれます。

>>> write_test('\r', text)

$ od -c write_test 
0000000    A  \r  \r   B  \r  \r                                        
0000006

>>> write_test('\r\n', text)

$ od -c write_test 
0000000    A  \r  \r  \n   B  \r  \r  \n                                
0000010

newline引数には、実際何を使えばいいの?

このようにnewline引数に渡す値によって動作が変わるので、実際にPythonでテキストファイルを読み書きするときは、どのように使い分けたら良いのでしょうか?

基本的には、改行コードはPythonに任せるのが最善でしょう。Pythonに任せるとは、つまりnewline引数を省略(あるいはNoneを指定)して、ファイルから読み込んだテキストは常に改行コードが「\n」になるようにし、反対にファイルへ書き出すときは「\n」をシステムデフォルトの改行コードに変換されるようします。

これは別の見方をすると、すべての改行コードのファイルにプログラムが対応していること意味します。

その他プログラムで扱うすべての文字列の改行コードは常に「\n」に統一することで、プログラムが分かりやすく間違いが起きにくくなります。

おわりに

プログラミングで改行コード問題は常についてまわる課題です。正しい知識を学んでバグを埋め込まないようにしましょう。

では、最後までお読み戴き誠にありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

・PM、SE、SIなどを20年以上経験
・ネットワークスペシャリスト、セキュリティスペシャリストなど複数の資格を保有

目次