4-1. ファイル入出力の基本

ファイル入出力の基本について説明します。

参考

ファイルのオープン

ファイルから文字列を読み込んだり、ファイルに書き込んだりするには、 まず、open() という関数によってファイルをオープンする(開く)必要があります。

[1]:
f = open('sample.txt', 'r')

変数 f には、ファイルを読み書きするためのデータが入ります。 これをファイルオブジェクトと呼びます。

'sample.txt' はファイル名で、そのファイルの絶対パス名か、このノートブックからの相対パス名を指定します。

ここでは、sample.txt という名前のファイルがこのノートブックと同じディレクトリにあることを想定しています。

たとえば、novel.txt というファイルが、ノートブックの1段上のディレクトリ(このディレクトリが入っているディレクトリ)にあるならば、'../novel.txt' と指定します。 ノートブックの1段上のディレクトリに置かれている data というディレクトリにあるならば、'../data/novel.txt' となります (4-3にもう少し詳しい解説があります)。

'r' はファイルをどのモードで開くかを指しており、'r'読み込みモードを意味します。 このモードで開いたファイルに書き込みすることはできません。

よく使われるモードは、次の3種類です。

引数 | モード

'r' | 読み込み 'w' | 書き込み 'a' | 追記

モードの引数がなかった場合は、'r' であると解釈されます。 書き込みについては後でも説明します。

モードの詳細は公式ドキュメントを参照。

ファイルのクローズ

ファイルオブジェクトを使い終わったら、原則として、close() メソッドを呼び出して、クローズする(閉じる)必要があります。

[2]:
f.close()

close() を呼び出さずに放置すると、そのファイルがまだ使用中だと認識されてしまいます。 これは、同じファイルを利用しようとする他のプログラムの働きを阻害します。(個室のトイレをイメージしてください。)

close() の呼び出しは重要ですが、忘れがちなものでもあります。 後述するwith文を使うのが安全です。

行の読み込み

ファイルオブジェクトには、readline() というメソッドを適用することができます。 ファイルから新たに1行を読んで文字列として返します。 この「1行」というのは、正確には、ファイルの先頭もしくは改行文字の次の文字から、ファイルの終わりもしくは改行文字までの文字列です。 1行は必ずしも改行文字で終わらないという点に注意して下さい。

ファイルの終わりに来たとき、readline()'' という空文字列を返します。

以下のようにして readline() を使ってファイルを行単位で読んでみましょう。

ファイルを読み終わると空文字列が返ることを確認してください。

[3]:
f = open('sample.txt', 'r')
[4]:
f.readline()
[4]:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n'
[5]:
f.readline()
[5]:
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n'
[6]:
f.readline()
[6]:
'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n'
[7]:
f.readline()
[7]:
''
[8]:
f.close()

readline() メソッドの呼び出しは、ファイルオブジェクトを消費します。 改めて読み出したいときには、再度オープンして新しいオブジェクトを使ってください。

練習

文字列 name をファイル名とするファイルの最後の行を文字列として返す関数 last_line(name) を定義してください。

[9]:
def last_line(name):
    ...

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[10]:
print(last_line('sample.txt')=="Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n")
False

ファイル全体の読み込み

ファイル全体を一括で読み込んで、1つの文字列を取得したいときには、read() メソッドを利用します。

[11]:
f = open('sample.txt', 'r')
f.read()
[11]:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n'

一度 read() を呼ぶと、ファイルの終端に達するので、それ以降は空文字列を返します。

[12]:
f.read()
[12]:
''
[13]:
f.close()

read() メソッドは、内部的には readline() メソッドを呼んでいます。 したがって、read() メソッドも同様にファイルオブジェクトを消費します。

練習

文字列 name をファイル名とするファイルをオープンして、 read() メソッドによってファイル全体を文字列として読み込み、 その文字数を返す関数 number_of_characters(name) を作成してください。

注意:return する前にファイルをクローズすることを忘れないようにしてください。

[14]:
def  number_of_characters(name):
    ...

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[15]:
print(number_of_characters('sample.txt') == 446)
False

編集中のファイルの動作

プログラムでファイルを開くと、そのプログラム内でそのファイルを閉じるまでは、他のプログラムでそのファイルを編集することはできません。

下のセルを実行した後で、Windowsならエクスプローラ、macOSならFinderで上のファイルを探して、削除してみてください。 「ファイルを閉じてから再実行してください。(Windowsの場合)」といったメッセージが出て、削除ができないはずです。

[16]:
f = open('test.txt', 'r')

下のセルを実行した後だと削除できます。

[17]:
f.close()

ファイルに対するwith文

ファイルのオブジェクトは、with文に指定することができます。

with ファイルオブジェクト as 変数:
    ...

with の次には、open によってファイルをオープンする式を書きます。

また、as の次には、ファイルのオブジェクトが格納される変数を書きます。

with文は処理後にファイルのクローズを自動的にやってくれますので、 ファイルに対して close() を呼び出す必要がありません。

[18]:
with open('sample.txt', 'r') as f:
    print(f.read())
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

ファイルへの書き込み

ファイルへの書き込みは、print 関数を使って行えます。 file 引数に書き込み先のファイルオブジェクトを指定します。 file は3.3で説明されているキーワード引数と呼ばれる引数ですので、 以下のように file=... という形で指定します。

[19]:
with open('print-test.txt', 'w') as f:
    print('hello\nworld', file=f)

文字列の中の \n は改行文字を表します。\nエスケープシーケンス(2-1に説明があります)の一種です。 エスケープシーケンスには、この他に、復帰文字を表す \r やタブを表す \t などがあります。

ファイルの読み書きのモードとしては、書き込みモードを意味する 'w' を指定しています。既に同じ名前のファイルが存在する場合は上書きされます(以前の内容はなくなります)。ファイルがない場合は、新たに作成されます。

'a' を指定すると、ファイルが存在する場合、既存の内容の後に追記されます。ファイルがない場合は、新たに作成されます。

print 関数は、デフォルトで、与えられた文字列の末尾に改行文字を加えて印字します。 末尾に加える文字は、end 引数で指定できます。

[20]:
with open('print-test.txt', 'a') as f:
    print('hello', 'world\n', end='', file=f) # 改行文字を加えない

また、複数の印字対象を渡すと、デフォルトで、空白文字で区切って印字します。 この区切り文字は、sep 引数で指定できます。

[21]:
with open('print-test.txt', 'a') as f:
    print('hello', 'world', sep=', ', file=f) # 'hello, world'が印字される

この他にも、ファイルオブジェクトには、より原始的な書き込み用メソッドが用意されています。 write() メソッドは、与えられた1つの文字列を単に書き込みます。 次に示すように、write() メソッドと read() メソッドは、対で使うことが良くあります。

[22]:
with open('sample.txt') as src, open('sample.txt.bak', 'w') as dst:
    dst.write(src.read())

このコードは、sample.txtsample.txt.bak にコピーします。

練習

2つのファイル名 infile, outfile を引数として、infile の半角英文字を全て大文字にした結果を outfile に書き込む file_upper(infile, outfile) という関数を作成してください。

なお、半角英文字の小文字を大文字に変換するには upper() というメソッドが使えます。 たとえば line という名前の変数に半角文字列が入っている場合、line.upper() とすれば大文字に変換した文字列を返します。

[23]:
def file_upper(infile,outfile):
    ...

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[24]:
with open('print-test.txt', 'w') as f:
    print('hello', 'world', file=f)
file_upper('print-test.txt', 'print-test-upper.txt')
with open('print-test-upper.txt', 'r') as f:
    print(f.read() == 'HELLO WORLD\n')
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
/tmp/ipykernel_5030/305307884.py in <module>
      2     print('hello', 'world', file=f)
      3 file_upper('print-test.txt', 'print-test-upper.txt')
----> 4 with open('print-test-upper.txt', 'r') as f:
      5     print(f.read() == 'HELLO WORLD\n')

FileNotFoundError: [Errno 2] No such file or directory: 'print-test-upper.txt'

ファイルの読み書きにおける文字コード指定

open でファイルを開くと、通常そのファイルをテキストモードで開きます(テキストモード以外にバイナリモードもあります)。

テキストモードでファイルを開くときは、さらに特定の文字コードによってそのファイルを開こうとします。 文字コードを指定しないと、デフォルトの文字コードでそのファイルを開こうとしますが、 この文字コードがファイルを書き込む際に指定したものと異なる場合、エラーが出たり文字化けしてしまいます。

デフォルトの文字コードは、WindowsはShift_JIS、macOSやLinuxはUTF-8になっていることが多いです。 UTF-8で文字を記録されたファイルをWindowsで、ただ open('utf-8.txt', 'w') のように文字コードを指定せずに開くとエラーが出ます。 同じく、Shift_JISで文字を記録されたファイルをmacOSで open('shift_jis.txt', 'w') として開くとエラーが出ます。

なお、この教材の冒頭で open('sample.txt', 'r') と、文字コードを指定せずにファイルを開きましたがエラーは出ませんでしたね。 これは、sample.txt では半角英数字しか使われておらず、半角英数字に関しては、Shift_JISもUTF-8も共通のルールでエンコードされているためです。

[25]:
# macOSならこちらでエラー
with open('shift_jis.txt', 'r') as f:
    print(f.read())

# Windowsならこちらでエラー
with open('utf-8.txt', 'r') as f:
    print(f.read())
---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
/tmp/ipykernel_5030/1730420542.py in <module>
      1 # macOSならこちらでエラー
      2 with open('shift_jis.txt', 'r') as f:
----> 3     print(f.read())
      4
      5 # Windowsならこちらでエラー

/usr/lib/python3.10/codecs.py in decode(self, input, final)
    320         # decode input (taking the buffer into account)
    321         data = self.buffer + input
--> 322         (result, consumed) = self._buffer_decode(data, self.errors, final)
    323         # keep undecoded input until the next call
    324         self.buffer = data[consumed:]

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte

特に半角英数以外の文字を記録する際は文字コードを指定すること、またそのようなファイルを開くときは、記録するときに指定した文字コードでファイルを開いてください。

文字コードは、open のキーワード引数として encoding='utf-8'(文字コードにUTF-8を指定する場合)のように指定することができます。

なお、日本語の文字コードには UTF-8, Shift_JIS, EUC-JP などがありますが、PythonではOSの種類に限らず、UTF-8という文字コードがよく使われます。本授業でもUTF-8を推奨します。

[26]:
# 文字コードを指定しないとmacOSならこちらでエラー
with open('shift_jis.txt', 'r', encoding='shift_jis') as f:
    print(f.read())

# 文字コードを指定しないとWindowsならこちらでエラー
with open('utf-8.txt', 'r', encoding='utf-8') as f:
    print(f.read())

# 文字コードを指定してファイルに書き込む場合
with open('text.txt', 'w', encoding='utf-8') as f:
    f.write('かきくけこ')
with open('text.txt', 'r', encoding='utf-8') as f:
    print(f.read())
あいうえお
あいうえお
かきくけこ

改行文字の削除

ファイルをテキストモードで開いて read()readline() を呼び出すと、 str 型の文字列として読み込まれます。

文字列の末尾にある改行文字の削除には、2-1で紹介した rstrip メソッドが使えます。 ただし、無引数で呼び出すと、改行文字以外の空白文字もまとめて削除されます。

[27]:
'あいうえお \n'.rstrip()
[27]:
'あいうえお'

削除する空白文字を改行文字 '\n' に限定したい場合には、 rstrip の引数に指定します。

[28]:
'あいうえお \n\n'.rstrip('\n')
[28]:
'あいうえお '

上の例から分かるように、末尾の改行文字をただ1つ削除したい場合には、 rstrip('\n') は適していません。 しかし、 readline() の返す文字列には、末尾に高々1つの '\n' しか存在しないので、 rstrip('\n') で問題ありません。

[29]:
with open('text/novel.txt', 'r', encoding='utf-8') as f:
    while True:
        line = f.readline()
        if line == '':
            break
        print(line)

print('------ 末尾の改行文字を削除すると以下のようになります-------')
with open('text/novel.txt', 'r', encoding='utf-8') as f:
    while True:
        line = f.readline()
        if line == '':
            break
        print(line.rstrip('\n'))
二人の若い紳士が、すつかりイギリスの兵隊のかたちをして、ぴか/\する鉄砲をかついで、白熊のやうな犬を二疋つれて、だいぶ山奥の、木の葉のかさ/\したとこを、こんなことを云ひながら、あるいてをりました。

「ぜんたい、こゝらの山は怪しからんね。鳥も獣も一疋も居やがらん。なんでも構はないから、早くタンタアーンと、やつて見たいもんだなあ。」

「鹿の黄いろな横つ腹なんぞに、二三発お見舞まうしたら、ずゐぶん痛快だらうねえ。くる/\まはつて、それからどたつと倒れるだらうねえ。」

------ 末尾の改行文字を削除すると以下のようになります-------
二人の若い紳士が、すつかりイギリスの兵隊のかたちをして、ぴか/\する鉄砲をかついで、白熊のやうな犬を二疋つれて、だいぶ山奥の、木の葉のかさ/\したとこを、こんなことを云ひながら、あるいてをりました。
「ぜんたい、こゝらの山は怪しからんね。鳥も獣も一疋も居やがらん。なんでも構はないから、早くタンタアーンと、やつて見たいもんだなあ。」
「鹿の黄いろな横つ腹なんぞに、二三発お見舞まうしたら、ずゐぶん痛快だらうねえ。くる/\まはつて、それからどたつと倒れるだらうねえ。」

練習の解答

[30]:
def  number_of_characters(name):
    f = open(name, 'r')
    s = f.read()
    f.close()
    return len(s)
[31]:
def last_line(name):
    last = ''
    with open(name, 'r') as f:
        while True:
            line = f.readline()
            if line == '':
                return last
            last = line
[32]:
def file_upper(infile,outfile):
    with open(infile, 'r') as f:
        with open(outfile, 'w') as g:
            g.write(f.read().upper())

以下のように1つのwith文に複数の open を書くこともできます。

[33]:
def file_upper(infile,outfile):
    with open(infile, 'r') as f, open(outfile, 'w') as g:
        g.write(f.read().upper())
[ ]: