ファイルの読み書き

前回は「The Python Tutorial」を基に、文字列フォーマットについて解説した。The Python Tutorialの次の説明は、ファイルからのデータの読み込み/ファイルへのデータの書き込みについてだ。ファイルの扱いについては、UNIXの基本的なファイルの扱いがそのままPython風になったような作りになっている。open()でファイルを開いてファイルオブジェクトを取得し、ファイルオブジェクトのread()やreadline()で読み込み、write()で書き込みができるといった仕組みだ。

標準入力からの読み込み/標準出力への書き込みでもかなりの処理を行うことができるが、「データとして複数のファイルを指定したい」とか、「出力先を複数にしたい」となってくると、入出力対象としてファイルを扱いたくなる。今回は、ファイルを入出力対象とする際の基本的な仕組みを紹介する。

ファイルの読み込み

次のサンプルはPythonでファイルを読み込むものだ。

daichi@ubuntu1804:~$ cat data.txt
A 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
B 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
C 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
D 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
E 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
F 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
G 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
H 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
I 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
J 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
K 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
L 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
M 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
N 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
O 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
P 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Q 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
daichi@ubuntu1804:~$ python3
Python 3.6.6 (default, Sep 12 2018, 18:26:19)
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f = open('data.txt','r')
>>> f.read()
'A 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nC 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nD 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nE 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nF 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nG 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nH 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nI 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nJ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nK 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nL 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nM 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nN 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nO 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nP 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nQ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.closed
False
>>> f.close()
>>> f.closed
True
>>>

このサンプルの処理の本質的な部分は、次の記述だ。

  1. f = open(‘data.txt’,’r’)
  2. f.read()
  3. f.close()


open()は2つの引数を取り、1つ目はファイルパス、2つ目はモードとなっている。「r」なら読み込み、「w」なら書き込み、「a」なら追記、「r+」なら読み書き、「b」ならバイナリファイル、といった具合だ。これらのモードは同時に複数指定することができ、例えば「rb+」と指定すれば「バイナリファイルを読み書き可能なものとして開く」、という指定になる。「b」が指定されていない場合には、データはテキストファイルとして扱われる。

read()は「ファイルの中身を全て持ってくる」という意味になる。read()は引数としてサイズを取ることができ、引数にサイズを指定した場合にはそのサイズまでファイルの中身を取ってくる。上記サンプルではサイズを指定していないので、ファイルの中身が全て読み込まれている。

close()は開いたファイルを閉じる処理だ。Pythonは明示的にclose()しなくても、いずれかのタイミングでガベージコレクタによって自動的にファイルのclose()処理が行われるのだが、それまで不要なリソースが確保され続けることになる。基本的に、不要になったらその場でclose()で閉じるというのがスマートなやり方だ。

1行ずつ読み込む

read()だとデータを丸ごと読み込んでしまうのだが、対象がテキストデータの場合には、まるごとではなく1行ごとにデータを読み込んでほしいことが多い。この場合に使えるのがreadline()だ。readline()では、次のように1行ずつデータを読み込んで返してくれる。

>>> f = open('data.txt','r')
>>> f.readline()
'A 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'B 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'C 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'D 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.closed
False
>>> f.readline()
'E 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'F 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'G 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'H 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'I 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'J 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'K 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'L 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'M 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'N 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'O 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'P 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
'Q 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.readline()
''
>>> f.readline()
''
>>> f.closed
False
>>> f.close()
>>> f.closed
True
>>>

先程のサンプルでは「f.closed」という書き方を使っているが、これはファイルオブジェクトがすでにclose()されたかどうかをチェックするものだ。Pythonだとあまりこの辺りが気になることはないのだが、オープンしたファイルはクローズするものということで、明示的にサンプルに掲載されている。

withを使ったプラクティス

チュートリアルではファイルをオープンして使用する方法として次のように「with as」を使う方法を紹介している。

>>> with open('data.txt','r') as f:
...     f.read()
...
'A 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nC 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nD 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nE 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nF 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nG 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nH 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nI 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nJ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nK 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nL 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nM 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nN 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nO 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nP 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\nQ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20\n'
>>> f.closed
True
>>> f.close()
>>> f.closed
True
>>>

サンプルを見てわかるように、この書き方をするとwithを抜けた段階で開いたファイルオブジェクトが自動的にクローズされることがわかる。クローズ処理が自動で行われるほか、途中で例外が発生した場合にもこの方法であればファイルをクローズすることができる。

forとの組み合わせ

Pythonではfor制御構文とファイルオブジェクトを組み合わせることが可能で、次のような書き方をするとファイルオブジェクトから自動的に1行ずつデータが読み込まれて処理が行われる。

>>> f = open('data.txt','r')
>>> i = 1
>>> for v in f:
...     print('{:d} - '.format(i),end='')
...     i += 1
...     print(v,end='')
...
1 - A 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
2 - B 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
3 - C 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
4 - D 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
5 - E 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
6 - F 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
7 - G 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
8 - H 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
9 - I 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
10 - J 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
11 - K 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
12 - L 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
13 - M 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
14 - N 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
15 - O 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
16 - P 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
17 - Q 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
>>>

「ファイルからデータを1行ずつ読み込んで処理を行う」というのはよくある処理だが、Pythonではそれをこのように簡単に記述することができる。

ファイルへの書き込み

ファイルへの書き込みは次のようにwrite()を使って行えばよい。

>>> f = open('data2.txt','w')
>>> f.write('A 0 1 2 3 4 5 6 7 8 9 10\n')
25
>>> f.write('B 0 1 2 3 4 5 6 7 8 9 10\n')
25
>>> f.write('C 0 1 2 3 4 5 6 7 8 9 10\n')
25
>>> f.write('D 0 1 2 3 4 5 6 7 8 9 10\n')
25
>>> f.write('E 0 1 2 3 4 5 6 7 8 9 10\n')
25
>>> f.close()
>>> f.closed
True
>>> quit()
daichi@ubuntu1804:~$ ls -l data2.txt
-rw-rw-r-- 1 daichi daichi 125 Apr  8 07:36 data2.txt
daichi@ubuntu1804:~$ cat data2.txt
A 0 1 2 3 4 5 6 7 8 9 10
B 0 1 2 3 4 5 6 7 8 9 10
C 0 1 2 3 4 5 6 7 8 9 10
D 0 1 2 3 4 5 6 7 8 9 10
E 0 1 2 3 4 5 6 7 8 9 10
daichi@ubuntu1804:~$

また、今回は取り上げていないが、seek()を使うとファイルの中の指し示す先を移動させることができる。基本的にOSが提供しているベーシックな機能はPythonでもそのまま利用できる仕組みになっている。

ファイルの読み書きができるようになると、俄然プログラミング言語感が出てくる。ここまでできるようになれば、後は文字列を解析する方法なんかを調べれば、ちょっとしたテキストデータの加工処理のようなものはコーディングして自動的に処理できるようになってくる。そこまで使えれば、システム運用していく上でかなり強力な”武器”になるはずだ。

【参考資料】
The Python Tutorial