【Python】テキストファイルを読み書きモードで開く(モードr+, r+t)【入門第64回】
目次
- テキストファイルの読み書き
- テキストファイルを読み書きモードで開く
- ファイルオブジェクトのタイプ
- テキストファイルの読み込み
- テキストファイルへの書き込み
- 同時に読み書きするサンプルプログラム
- おわりに
- 関連動画
テキストファイルの読み書き
こんにちは、narupoです。
今回から少し高度なファイル入出力についてやっていきます。
今回はテキストファイルを読み書きモードで開く方法を解説します。
テキストファイルの入出力処理を書いていると、テキストファイルを読みながら同じファイルに書き込みもしたい時があります。
そういうときはopen
関数のmode
引数に文字列r+
かr+t
を指定してテキストファイルを開きます。
r+
またはr+t
モードで開いたテキストファイルは読み込みと書き込みが両方可能になります。
このモードで得られるファイルオブジェクトのタイプは_io.TextIOWrapper
です。
ファイルオブジェクトのメソッドを使うとファイルに読み書きすることが出来ます。
今回は具体的には↓を見ていきます。
テキストファイルを読み書きモードで開く
ファイルオブジェクトのタイプ
テキストファイルの読み込み
テキストファイルへの書き込み
同時に読み書きするサンプルプログラム
ほかのファイル入出力についての入門記事は↓をご覧ください。
- 【Python】ファイルを開いて閉じる。open関数の使い方【入門第54回】
- 【Python】テキストファイルの読み込み(モードr, rt)【入門第55回】
- 【Python】テキストファイルへの書き込み(モードw, wt)【入門第56回】
- 【Python】テキストファイルへの追記(モードa, at)【入門第57回】
- 【Python】テキストファイルを排他的に開いて書き込む(モードx, xt)【入門第58回】
- 【Python】バイナリファイルの読み込み(モードrb)【入門第59回】
- 【Python】バイナリファイルへの書き込み(モードwb)【入門第60回】
- 【Python】バイナリファイルへの追記(モードab)【入門第61回】
- 【Python】バイナリファイルを排他的に開いて書き込む(モードxb)【入門第62回】
- 【Python】テキストファイルを読み書きモードで開く(モードr+, r+t)【入門第63回】
- 【Python】テキストファイルを読み書きモードで新規作成して開く(モードw+, w+t)【入門第64回】
- 【Python】テキストファイルを読み書きモードで、新規作成して、排他的に開く(モードx+, x+t)【入門第65回】
- 【Python】バイナリファイルを読み書きモードで開く(モードr+b)【入門第66回】
- 【Python】バイナリファイルを読み書きモードで新規作成して開く(モードw+b)【入門第67回】
- 【Python】バイナリファイルを読み書きモードで排他的に開く(モードx+b)【入門第68回】
- 【Python】テキストファイルを読み書きモードで追記する(モードa+)【入門第69回】
- 【Python】バイナリファイルを読み書きモードで追記する(モードa+b)【入門第70回】
- 【Python】バイナリファイルのランダムアクセスの基本・前編【入門第71回】
- 【Python】バイナリファイルのランダムアクセスの基本・後編【入門第72回】
- 【Python】ランダムアクセスによる固定長レコードの読み書き・前編【入門第73回】
テキストファイルを読み書きモードで開く
テキストファイルを読み書きモードで開くにはopen
関数を↓のように使います。
:::python
with open('animals.txt', mode='r+', encoding='utf-8') as fp:
pass
open
関数の第1引数にファイルのパスを指定します。
mode
引数にはr+
またはr+t
を指定します。
encoding
にはテキストファイルのエンコーディングを指定します。
r+
またはr+t
モードは、開こうとするファイルが存在しない場合は例外FileNotFoundError
を送出します。
:::python
try:
with open('not-found', mode='r+', encoding='utf-8') as fp:
pass
except FileNotFoundError:
print('ファイルが見つかりません')
↑のコードの実行結果は↓のようになります。
:::text
ファイルが見つかりません
open
関数の具体的な使い方については↓の記事をご覧ください。
ファイルオブジェクトのタイプ
r+
またはr+t
モードで開いたファイルオブジェクトのタイプは_io.TextIOWrapper
です。
:::python
with open('animals.txt', mode='r+', encoding='utf-8') as fp:
print(type(fp))
↑のコードの実行結果は↓のようになります。
:::text
<class '_io.TextIOWrapper'>
このTextIOWrapper
はTextIOBase
を継承したクラスです。
さらにTextIOBase
はIOBase
を継承しています。
TextIOBase
とIOBase
の公式ドキュメントを見ればこのファイルオブジェクトで使えるメソッドを一望できます。
- io --- ストリームを扱うコアツール - TextIOBase — Python 3.7.4 ドキュメント
- io --- ストリームを扱うコアツール - IOBase — Python 3.7.4 ドキュメント
テキストファイルの読み込み
↓のような内容のテキストファイルanimals.txt
の読み込みをやってみます。
:::text
猫
犬
猿
readlines
メソッドを使って行のリストとしてテキストファイルを読み込むには↓のようにします。
:::python
with open('animals.txt', mode='r+', encoding='utf-8') as fp:
lines = fp.readlines()
print(lines)
↑のコードの実行結果は↓のようになります。
:::text
['猫\n', '犬\n', '猿\n']
その他のメソッドの使い方については↓の記事をご覧ください。
テキストファイルへの書き込み
↓のような内容のテキストファイルanimals.txt
への書き込みをやってみます。
:::text
猫
犬
猿
r+
またはr+t
モードで開いたファイルオブジェクトのオフセット位置はファイルの先頭、つまりtell
メソッドが0
を返す位置にあります。
:::python
with open('animals.txt', mode='r+', encoding='utf-8') as fp:
print(fp.tell())
↑のコードの実行結果は↓のようになります。
:::text
0
この状態でファイルに書き込みをするとどうなるのでしょうか?
やってみましょう。
:::python
with open('animals.txt', mode='r+', encoding='utf-8') as fp:
fp.write('ポチ')
↑のコードを実行するとanimals.txt
の中身は↓のようになります。
:::text
ãƒãƒŠ¬
猿
ぎえー!
壊 れ て し ま い ま し た 。
このようにテキストファイルのオフセット位置が先頭の時などに文字列を書き込んだりすると、高い確率でファイルの内容が壊れます。
なぜかというと、テキストファイルはバイナリファイルの一種だと前回お話ししました。
つまりテキストファイルに書かれているデータというものはバイトの列になっているんですね。
で、文字列を書き込んだ場合はエンコーディングされたバイト列が書き込まれています。
このバイトの列の途中を別のデータで書き替えたりすると、データを文字列にデコードすることが出来なくなって文字化けなどを起こしてファイルが壊れます。
たとえば「あ」という文字1つを見てみても、バイト列にした場合は数バイト必要な場合があります。
ほかの漢字などもそうです。ものによっては1バイト~数バイトのバイト数の差があります。
この並びを上書きなどで壊してしまうと文字化けが起こるんですね。
よって、基本的にテキストファイルに変更を加えたい場合は「一括変更」か「追記」に限られます。
ファイルのオフセット位置を変更するにはseek
メソッドを使います。
:::python
import os
with open('animals.txt', mode='r+', encoding='utf-8') as fp:
print(fp.tell())
fp.seek(0, os.SEEK_END)
print(fp.tell())
seek
メソッドの第1引数には第2引数からの相対位置を、第2引数には基点位置を指定します。
os.SEEK_END
はファイルの末尾を表す定数です。この定数を使うにはos
モジュールのインポートが必要です。
↑のコードの実行結果は↓のようになります。
:::text
0
15
tell
メソッドの返す値はテキストファイルの場合は不明瞭な値ですが、オフセット位置が進んでいるのはわかると思います。
この状態でwrite
メソッドを呼び出すとテキストファイルの末尾に追記することが出来ます。
:::python
import os
with open('animals.txt', mode='r+', encoding='utf-8') as fp:
fp.seek(0, os.SEEK_END)
fp.write('加えられた豚\n')
↑のコードを実行するとanimals.txt
の中身は↓のようになります。
:::text
猫
犬
猿
加えられた豚
豚が加わっていますね。
その他の書き込み用メソッドについては↓の記事をご覧ください。
同時に読み書きするサンプルプログラム
サンプルプログラムとしてちょっと変わったプログラムをご紹介します。
このプログラムはanimals.txt
というテキストファイルに、プログラムを実行するたびに「にゃーん」と追記していくプログラムです。
:::python
import os
fname = 'animals.txt'
if not os.path.exists(fname):
with open(fname, mode='w', encoding='utf-8') as fout:
fout.write('にゃーん\n')
else:
with open(fname, mode='r+', encoding='utf-8') as fp:
line = fp.readline()
fp.seek(0, os.SEEK_END)
fp.write(line)
まず↓の所でファイルが存在しなかった場合に「にゃーん」と1行だけ書かれたテキストファイルを新規作成します。
:::python
if not os.path.exists(fname):
with open(fname, mode='w', encoding='utf-8') as fout:
fout.write('にゃーん\n')
↓の所でファイルの1行目を読み込んで(つまり「にゃーん」を読み込んで)、ファイルのオフセット位置を末尾に移動して行を追記しています。
:::python
else:
with open(fname, mode='r+', encoding='utf-8') as fp:
line = fp.readline()
fp.seek(0, os.SEEK_END)
fp.write(line)
なんて変なプログラムだ
おそろしい
おわりに
今回はテキストファイルの読み書きを通して少しだけランダムアクセスもやってみました。
テキストファイルのランダムアクセスはある意味バイナリファイルよりも難しい気がします。
以上、次回に続きます。
また見てね