あるファイルを一度開いてから閉じるまでに読み込み処理と書き込み処理の両方を行いたいとき、その両方を行える “読み書き” モードで開くことになる。そのときの挙動についてをまとめておく。
本記事公開時点は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
先にファイルの読み込み処理を行って内容を記憶しておき、その後新しいデータを同じファイルへ書き込む、という流れで作業をしたい場合がある。
このとき
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