Pythonでファイルを読み書きモードで開く

Last modified: 2021-10-14

あるファイルを一度開いてから閉じるまでに読み込み処理と書き込み処理の両方を行いたいとき、その両方を行える “読み書き” モードで開くことになる。そのときの挙動についてをまとめておく。

本記事公開時点はPython 2系用のコードを掲載していたが、Python 2系は更新が終了したため、Python 3系以上向けの書き方に修正している。

なお、ここでは、Python固有の機能について扱っているわけではなく、他の言語でも同じような要領で作業が行える。

読み書きモード

ファイルを開く際の指定

組み込み関数open()の2番目の引数(キーワード引数名はmode)にr+を指定する。

f_io = open([ファイルの場所], "r+")

読み込み

読み込みだけを行ったときの挙動は、当然と言えば当然なのだが、読み込み専用モードでファイルを開いたときと同じ。

読み書きモードにおける読み込みのテスト
#! /usr/bin/python
# -*- coding: utf-8 -*-

import sys

iofile = "data.txt"

# 準備として適当なデータを書き込んでおく
try:
    with open(iofile, "w") as f_out:
        f_out.write("""abcdefg
hijklmnop
qrstuv
wxyz""")
except IOError as e:
    sys.exit('Cannot write to "{}": {}'.format(iofile, e))

# 読み込む処理は読み込み専用モードと同様
try:
    with open(iofile, "r+") as f_io:
        print("content:")
        for line in f_io:
            print(line.strip())
except IOError as e:
    sys.exit('Cannot read from "{}": {}'.format(iofile, e))

上のコードは4行のアルファベット文字列をファイルdata.txtに書き込んだ後、読み書きモードで開いて読み込み処理のみを行い、読み込み専用モードで開いたときと同様、その各行を表示する。

出力結果
content:
abcdefg
hijklmnop
qrstuv
wxyz

書き込み

読み込み処理は一切行わずに書き込み処理のみを行うと、(そのままでは)ファイルの先頭からデータを上書きしていくが、書き込んだ長さが元のデータより短いと、書き込んだデータが終わったところより後ろは元のデータが残る。

読み書きモードにおける書き込みのテスト
#! /usr/bin/python
# -*- coding: utf-8 -*-

import sys

iofile = "data.txt"

# 準備として適当なデータを書き込んでおく
try:
    with open(iofile, "w") as f_out:
        f_out.write("""abcdefg
hijklmnop
qrstuv
wxyz""")
except IOError as e:
    sys.exit('Cannot write to "{}": {}'.format(iofile, e))

# 読み書きモードで開いた状態でそのまま書き込むと先頭から上書きしていく
try:
    with open(iofile, "r+") as f_io:
        f_io.write("3.14159265358979")
except IOError as e:
    sys.exit('Cannot write to "{}": {}'.format(iofile, e))

# 開き直して中身を見ると、書き込んだ部分の長さだけ先頭から上書きされている
try:
    with open(iofile, "r") as f_io:
        print("content:")
        for line in f_io:
            print(line.strip())
except IOError as e:
    sys.exit('Cannot read from "{}": {}'.format(iofile, e))

実行結果は下のようになる。書き込んだのが16バイト、上書きされたのも16バイト(改行の1バイトを含む)。

出力結果
content:
3.14159265358979p
qrstuv
wxyz

読み込み後書き込みを行う

先にファイルの読み込み処理を行って内容を記憶しておき、その後新しいデータを同じファイルへ書き込む、という流れで作業をしたい場合がある。

このとき

  • データを書き込む前に読み書き位置を先頭に移動する・Pythonのファイルオブジェクトにはrewind()というメンバ関数はないため、seek()に引数0を指定する
  • データを書き込んだ後、その後ろのデータは捨てる(ファイルを切り詰める)・ファイルオブジェクトのメンバ関数truncate()を使用

という作業が必要になる。

読み書きモードにおける読み込み後の書き込みのテスト
#! /usr/bin/python
# -*- coding: utf-8 -*-

import sys

iofile = "data.txt"

# 準備として適当なデータを書き込んでおく
try:
    with open(iofile, "w") as f_out:
        f_out.write("""abcdefg
hijklmnop
qrstuv
wxyz""")
except IOError as e:
    sys.exit('Cannot write to "{}": {}'.format(iofile, e))

try:
    with open(iofile, "r+") as f_io:
        # 内容の読み込み(記憶)
        lines = []
        for line in f_io:
            lines.append(line)
        # readlines()を代わりに使っても全てを読み込んでリストに入れることができる
        #lines = f_io.readlines()

        # 読み書きモードで読み込み後にデータを書き込む一連の流れ
        f_io.seek(0)                    # 先頭に書き込み位置を移動
        f_io.write("3.14159265358979")  # 内容の書き込み
        f_io.truncate()                 # 書き込んだ位置までで切り詰める
except IOError as e:
    sys.exit('Cannot read from / write to "{}": {}'.format(iofile, e))

# 中身はwrite()で書き込んだ内容のみとなる
try:
    with open(iofile, "r") as f_io:
        print("content:")
        for line in f_io:
            print(line.strip())
except IOError as e:
    sys.exit('Cannot read from "{}": {}'.format(iofile, e))

# 読み書きモードで開いた直後に読み込んで記憶したものを表示
print('\nloaded:')
for line in lines:
    print(line.strip())
出力結果
content:
3.14159265358979

loaded:
abcdefg
hijklmnop
qrstuv
wxyz

追加書き込みの読み書きモード

r+の代わりにa+のモードを指定すると、読み書きの両方はできるものの、書き込みに関しては開いた時点で存在している部分のデータは上書きされず、その後ろに追加される形で行われることになる。

元の記事の公開時点(Python 2系を使用)では必要なかったが、Python 3.9系時点では、a+モードで開いたファイルから読み込みを行う前には、ファイルオブジェクトに対してseek(0)を呼んで先頭に読み書き位置を移動しておかないと、読み込まれる内容が空になる。

追加書き込みの読み書きモードにおける読み込み後の書き込みのテスト
#! /usr/bin/python
# -*- coding: utf-8 -*-

import sys

iofile = "data.txt"

# 準備として適当なデータを書き込んでおく
try:
    with open(iofile, "w") as f_out:
        f_out.write("""abcdefg
hijklmnop
qrstuv
wxyz""")
except IOError as e:
    sys.exit('Cannot write to "{}": {}'.format(iofile, e))

try:
    with open(iofile, "a+") as f_io:
        # 内容の読み込み
        print("before:")
        # 読み込みを行う前に先頭に読み書き位置を移動しておく
        f_io.seek(0)
        for line in f_io:
            print(line.strip())

        # 書き込んだデータは最後に追加される
        f_io.write("3.14159265358979")
except IOError as e:
    sys.exit('Cannot read from / write to "{}": {}'.format(iofile, e))

# 開いた時点で存在している部分は上書きされていない
try:
    with open(iofile, "r") as f_io:
        print("\nafter:")
        for line in f_io:
            print(line.strip())
except IOError as e:
    sys.exit('Cannot read from "{}": {}'.format(iofile, e))
出力結果
before:
abcdefg
hijklmnop
qrstuv
wxyz

after:
abcdefg
hijklmnop
qrstuv
wxyz3.14159265358979