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

この記事ではPython3のopen()関数でテキストファイルを読み込むとき改行コードはどうなるの?プログラムがテキストをファイルに書き込むとき改行コードは何になるの?と言った疑問にお答えします。

改行コードとは

プログラムを書く方はご存知だろうが、OSによって使用される改行コードは異なります。現在使われている改行コードには次の3つがあります。

LF
Line Feed(ラインフィード)と呼ばれる制御文字。Unix、Linux、Macで改行コードとして使われている。エスケープシーケンスで表すと「\n」。
CR
Carrige Return(キャリッジリターン)と呼ばれる制御文字。古いMacで改行コードとして使われていた。エスケープシーケンスで表すと「\r」。最近のMacではLFが使われているので、もはやこの改行コードのテキストに出会うことはないかも。
CR+LF(CRLF)
上記の2つの制御文字が連続したもの。Windowsがこれを改行コードとして使っている。

改行コードの違いを意識してプログラミングしないとトラブルになることがあります。

例えばCRLFを期待しているプログラムがLFのみのテキストファイルを読み込むと、CRLFが一つも見つからないので、テキスト全体を1行として読み込んでしまいます。

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

なお改行コードCRは、現在あまり使われていませんので、この記事では詳しく扱いません。

open()関数のnewline引数

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

なお、改行コードはテキストファイル特有のものですので、ファイルをテキストモード以外で開いた場合は改行コードを気にする必要はありません(改行コードは制御文字なのでそもそも改行コードは存在しない)。

この後はにファイルを読み込むときと書き込むときに分けて順に説明します。

open()関数でテキストファイルを読み込むときの改行コード

まずはopen()関数でテキストファイルを読み込んだときの改行コードについて説明します。

newline引数に渡す値と、そのときの動作を次の表にまとめました。

newline引数の値動作の説明
None universal newlinesモードが有効になる。従って、読み込むとき「\n」、「\r」、「\r\n」を改行コードと認識する。認識された改行コードは呼び出し元へ返す前に「\n」に変換される。
'' universal newlinesモードが有効になる。従って「\n」、「\r」、「\r\n」を改行コードと認識する。改行コードは変換されないでそのまま呼び出し元へ返される。
'\n', '\r', '\r\n' newline引数に指定された文字列を改行コードと認識する。改行コードは変換されず、そのまま呼び出し元へ返される。

newline引数のデフォル値はNoneです。つまりnewline引数を省略するとNoneを渡すのと同じです。

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

つまり、universal newlinesモードが有効であれば、プログラムではテキストファイルにどんな改行コードが使われているかを意識せずに、行の処理ができるようになります。

newline数にそれぞれの値を渡したときの実際の動作例は、後ほど説明します。

open()関数でテキストファイルに書き込むときの改行コード

open()関数でテキストファイルにテキストを書き込むとき、newline引数に渡す値にどのような動作になるかは、次の表のようになります。

newline引数動作の説明
None 書き出すテキストの「\n」を、システムデフォルトの改行コード os.linesep に変換する。
'' または '\n' テキストは何も変更されず、そのまま書き出される。
'\r' または '\r\n' 書き出すテキストの「\n」は、 newline 引数に渡された文字列に変換される。

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

テキストを書き出すときの動作例もこの後詳しく説明します。

テキストファイル読み込み時の動作を確認する

ここからは実際にコードを打ち込んで、実際の動作を確認してみます。

最初にテキストファイル読み込み時の動作を確認します。

読み込み時の確認方法

改行コードは次の方法で確認します。テキストファイルはodコマンドを使って確認します。

ここで読み込むテキストファイルとしてCRLF.txtとLF.txtを使います。odコマンドを使ってファイルの内容を確認します。

$ od -c CRLF.txt
0000000    c   r   l   f  \r  \n   c   r   l   f  \r  \n
0000014
$ od -c LF.txt
0000000    l   f  \n   l   f  \n
0000006

ファイルは次のread_test()関数を定義して読み込みます。この関数に渡された引数fとnlは、それぞれopen()関数のファイル名とnewlineに渡されます。readlines()関数は、各行をリストにまとめますので、その戻り値をそのままread_test()関数の戻り値とし返します。

readlines()関数は改行コードで行を分割しますので、これで改行コード分割された行を確認します。

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

read_test()関数は各行を要素に持つリストを返すので、それを対話型インタープリタに表示させます。対話型インタープリタは改行コードをエスケープシーケンスとして表示するため、改行コードを目で見て確認できます。

テキストファイル読み込み時の動作テスト

前述のテキストファイルと表示プログラムを使用して、改行コードがどのようになるか見てみましょう。

newline引数に「None」を渡す

open()関数のnewline引数に「None」(newline引数を省略したときもデフォルトのNoneになる)を渡して呼び出すと、universal newlinesモードが有効になります。そして認識した改行コードを呼び出し元に返す前に「\n」へ変換します。

次のCRLF.txtを読み込む例を見てみましょう。行は正しく分割され、改行コードは「\n」に置き換わっています。

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

最初なので詳しく説明しましょう。ただし、実際にコードを確認したわけではないので厳密には異なるかもしれませんが、実用上はこのように理解しておけば十分でしょう。

  1. read_test()関数を呼び出すと、「’CRLF.txt'」と「None」を引数にopen()関数を呼び出す。
  2. open()関数が返したファイルオブジェクトに対してreadlines()メソッドを呼び出す。
  3. readlines()メソッドは、読み込んだテキストファイルから改行コードを「\r\n」と認識し、「\r\n」で行を分割すると共に「\n」に置き換える。そして各行を要素とするリストを呼び出し元に返す。
  4. readlines()メソッドがリストを返したら、それをそのままread_test()関数が呼び出し元に返す。
  5. read_test()関数が返したリストは、対話型インタープリタによって表示される。

次にLF.txtを読み込む例を見てみましょう。

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

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

newline引数に「''」を渡す

open()関数のnewline引数に「''」を渡すと、universal newlinesモードが有効になります。しかし、認識した改行コードを変換することはしない。

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

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

CRLF.txtを読んだ場合、改行コードは「\r\n」と認識されますが、「\r\n」は変換されずそのままです。

LF.txtを読んだ場合、改行コードは「\n」と認識されるが、同様に「\n」は変換されずそのままです。

newline引数に「'\n'」を渡す

open()関数のnewline引数に「'\n'」を渡すと、「\n」が改行コードと認識されるが、改行コード「\n」は変換されずにそのまま返される。

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

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

CRLF.txt、LF.txtのどちらを読み込んでも、改行コードは「\n」と認識される。そして「\n」は変換されない。

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

open()関数のnewline引数に「'\r\n'」を渡すと、改行コードは「\r\n」と認識されるが、「\r\n」を変換しないでそのまま返す。

実行例は次の通りです。

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

CRLF.txtを読み込むと、改行コードを「\r\n」と認識するが、「\r\n」は変換されずそのままです。

一方、LF.txtを読み込むと、改行コードを「\r\n」と認識するので、改行コードが見つからず1行として返されている。

テキストファイル書き込み時の動作を確認する

今度はテキストをファイルに書き込むときの動作を確認してみましょう。

テキストファイルに書き込んだときの確認方法

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

>>> def write_test(nl, txt):
...   open('write_test', 'wt', newline=nl).write(txt)
...

書き込むテキストには「'A\r\nB\r\n'」を使います。これで確認したいことはすべてカバーできますので、「'A\nB\n'」のようなテキストの書き込み確認は行いません。

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

テキストファイルへの書き込み時の動作テスト

それでは確認用のプログラムを使って改行コードがどうなるかみていきましょう。

newline引数に「None」を渡す。

open()関数のnewline引数に「None」を渡すと、「\n」を os.linesep に置き換えて出力する。

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

>>> write_test(None, 'A\r\nB\r\n')

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

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

>>> write_test(None, 'A\r\nB\r\n')
$ od -c write_test
0000000    A  \r  \r  \n   B  \r  \r  \n
0000010

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

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

>>> write_test('', 'A\r\nB\r\n')

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

>>> write_test('\n', 'A\r\nB\r\n')

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

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

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

>>> write_test('\r\n', 'A\r\nB\r\n')

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

実際にプログラミングするときには何を使えば良いか?

このようにnewline引数に渡す値によって動作が変わるので、実際にプログラムでテキストファイルを読み書きするときには、何をつかたたら良いでしょうか?

ここでは一つの指針を示したいと思います。

  • プログラムの中では「\n」だけを使う。

    プログラムの中で作成するテキストも、ファイルから読み込んだテキストも「\n」だけになるよう統一しましょう。それにはファイルを読み込むときには、open()関数のnewline引数に「None」を指定(あるいはnewline引数を省略)します。

    読み込むファイルの改行コードが絶対に特定のもであると確信できるのであれば、「None」以外のものを使っても良いでしょう。

    このようにプログラムの中で使う改行コードを統一しておけば、「改行コードはなんだったけ?」と混乱することもありません。

  • プログラムからテキストをファイルに書き出すときは、要件に従って選択する。

    例えばプログラムをWindowsでもLinuxでも動作するように可搬性を持たせるなら、newline引数に「None」を指定(あるいはnewline引数を省略)て、OSの改行コードでファイルに書き込むようにします。

    プログラムのテキストを勝手に変換されないようにしたいなら、「''」を指定すればいいでしょうし、改行コードを「\r\n」で書き出したければ「\r\n」を指定すれば良いでしょう。

  • 読み込むときに「改行コードを変換されてほしくない」という特別な場合のみ、ファイル読み込み時にnewline引数に「''」を使う

おわりに

プログラミングで改行コード問題は常についてまわる課題です。正しい知識を学んでバグを埋め込まないようにしましょう。最後までお読み戴き誠にありがとうございました。