Q1with open("data/notes.txt", "r") as f:の第 2 引数"r"は何を表しますか?
ファイル入出力 — with open() で安全に読み書きする
Pythonのファイル入出力をwith open()で解説。read/readlines/readlineの使い分け、セイウチ演算子での1行読み、wとaモードの違い、入力を加工して別ファイルに保存する集計まで確認できます。
プログラムが扱うデータの多くは、ハードディスク上のファイルとして保存されています。タスクリストを読み込んで一覧表示する、完了したタスクをログに書き出す、設定値をファイルから読む — どれも実務で頻繁に発生する操作です。
Python ではwith open()を使えば、これらをひとまとまりの安全な構文で記述できます。この記事ではwith open()の基本形から、読み込み 3 種 (read / readlines / readline)、書き込み (w) と追記 (a)、そしてフォルダ階層を持つパスの扱いまで、実際にファイルを動かしながら身につけます。
with open() でファイルを安全に開閉する
「ファイルを開く」というのは OS にリソースを掴ませる操作で、開いたら必ず閉じる必要があります。閉じ忘れるとファイルディスクリプタが枯渇したり、書き込み内容がバッファに残ったままディスクに反映されないといった問題が起きます。
open()は OS にファイルディスクリプタ (FD)を確保させる操作。close()を呼ぶまでFD は掴まれたまま、書き込みもバッファに残ったまま。閉じ忘れは深刻なリソースリークになる。ファイルディスクリプタ (FD) とは
FD (file descriptor)は OS が「いま開いているファイル」 1 つひとつに割り振る整数の IDです。open()のたびに新しい FD が払い出され、close()で OS に返却されます。プロセスが同時に持てる FD の数にはOS が決めた上限(Linux は既定で約 1,024 個)があり、これを使い切ると新しいopen()がOSError: Too many open filesで失敗するようになります。閉じ忘れの怖さはこの「数が決まっている資源を握ったままになる」点にあります。
with open(path, mode) as f:の形で書くと、ブロックを抜けた瞬間に自動でファイルが閉じられるため、こうしたミスがそもそも起こせません。as fのfは開いたファイルオブジェクトを受ける変数で、withブロックの中ではこのf経由で読み書きします。
パスはスラッシュ /でフォルダを区切ります。たとえばopen("data/tasks.txt", "r")と書けば、カレントディレクトリのdataフォルダの中にあるtasks.txtを開けます。Windows の\ではなく/を使うのが Python の慣習です(内部で OS ごとに変換されます)。
withのブロックに入った瞬間にファイルが開かれas fに入る。ブロックを抜けるときは正常終了でも例外でも、自動で close()が走る。読み込み — read / readlines / readline
ファイルを読み込むにはopen(path, "r")のr(read = 読み込み)モードを使います。返ってきたファイルオブジェクトには3 種類の読み込みメソッドがあり、用途に応じて使い分けます。
- f.read() — ファイル全体を1 つの文字列として一気に読み込む
- f.readlines() — ファイル全体を行ごとの文字列リストとして読み込む
- f.readline() — 1 行だけ読み込む。繰り返し呼ぶと次の行が返り、末尾に達すると空文字列""が返る
# 全文を 1 つの文字列で取得
with open("data/tasks.txt", "r") as f:
content = f.read()
print(type(content)) # <class 'str'>
# 行ごとのリストで取得
with open("data/tasks.txt", "r") as f:
lines = f.readlines()
print(type(lines)) # <class 'list'>
# 1 行ずつ取得(次の呼び出しで次行)
with open("data/tasks.txt", "r") as f:
first = f.readline() # "資料を作成する\n"
second = f.readline() # "メールを返信する\n"
ここから先はブラウザ用の仮想ファイルシステム (VFS) で動かします
右のコンソールはブラウザ内の仮想的なファイルシステムで、data/tasks.txtなどの教材ファイルがあらかじめ用意されています。ローカル PC で同じコードを動かす場合は、実行する Python ファイル(例: main.py)と同じ階層に data/ フォルダを作り、その中に tasks.txt を配置してから実行してください。パスの書き方("data/tasks.txt"のように/で区切る)は本物の Python と完全に同じです。
1 行 = 1 件のレコードという形のファイル(CSV のヘッダ抜き、改行区切りのリスト、ログなど)を扱うときは、f.readlines()が便利です。返り値は行ごとに分割された文字列のリストで、各要素は末尾に改行\nが付いた状態です。
そのままprint()すると改行が二重になって読みづらいため、各行に.rstrip("\n")を当てて末尾の改行を落とすのが定石です。
大きなファイルは 1 行ずつ — readline とセイウチ演算子
f.read()やf.readlines()はファイル全体をメモリに展開します。テキストの設定ファイル程度なら問題ありませんが、サーバーログのように数 GBに達するファイルでは、メモリが足りずプログラムがクラッシュすることがあります。
f.readline()を使うと1 行だけをメモリに読み込めるため、メモリ使用量は常に 1 行分で済みます。readline()はファイル末尾に達すると空文字列 ""を返すので、これを使ってループの終端を判定します。
""を返してループを抜ける合図になる。# 古典的な書き方: 空文字 "" を見たら break
with open("data/log.txt", "r") as f:
while True:
line = f.readline()
if not line: # 空文字 = ファイル末尾
break
print(line.rstrip())
セイウチ演算子 := で同じことが 1 行少なく書ける
Python 3.8 で導入されたセイウチ演算子 (walrus operator) :=を使うと、whileの条件式の中で代入と判定を同時に行えます。while line := f.readline():と書けば、「readline()の結果をlineに入れて、それが空文字でない間ループを続ける」 というコードを 1 行で表現できます。
書き込み — w モードと a モード
ファイルへの書き込みは、open()の第 2 引数を書き込み系のモードに切り替えるだけです。実用上覚えておきたいのは 2 つです。
- "w"(write)— ファイルを新規作成 or 全上書き。既存内容は消える
- "a"(append)— ファイルの末尾に追記。既存内容は残る
書き込みメソッドも 2 つあります。f.write(s)は文字列sをそのまま書き、f.writelines(lst)は文字列のリストをまとめて書き込みます。書き込み先のパスにフォルダを含めれば、その中にファイルが作られます(フォルダがあらかじめ存在している前提です)。
# w モード: 文字列を 1 件ずつ書く
with open("data/done.txt", "w") as f:
f.write("資料を作成する\n")
f.write("メールを返信する\n")
# w モード: リストをまとめて書く(writelines)
with open("data/done.txt", "w") as f:
f.writelines(["資料を作成する\n", "メールを返信する\n", "会議に参加する\n"])
# a モード: 末尾に追記
with open("data/done.txt", "a") as f:
f.write("買い物をする\n")
改行 \n は自動で入らない
print()と違い、f.write()もf.writelines()も改行を勝手に入れてくれません。f.write("Hello")を 2 回呼んでもファイルにはHelloHelloと並ぶだけです。意図した位置で改行したいときは明示的に \n を入れるのが鉄則です。
発展 — ファイルを分析して結果を別ファイルに保存する
実務でもっとも頻繁に書く処理の 1 つが、入力ファイルを読み込んで何らかの分析を行い、結果を別のファイルに書き出すという流れです。open()を 2 回使い、片方は"r"、もう片方は"w"にするだけで実現できます。入力 → 加工 → 出力という 3 段階を、それぞれ別々のwithブロックに分けて書くのが読みやすくする定石です。
# data/log.txt を分析し、結果を data/log_summary.txt に保存する
# ① 入力: ログを読み込んで行リストにする
with open("data/log.txt", "r") as src:
lines = src.readlines()
# ② 加工: 件数と最初/最後のエントリを集計
total = len(lines)
first = lines[0].rstrip()
last = lines[-1].rstrip()
summary = f"件数: {total}\n最初: {first}\n最後: {last}\n"
# ③ 出力: 別ファイルに書き出す
with open("data/log_summary.txt", "w") as dst:
dst.write(summary)
print("分析結果を data/log_summary.txt に保存しました")
文字エンコーディングは encoding="utf-8" が定石
実機の Python ではopen(path, "r", encoding="utf-8")のようにエンコーディングを明示します。Shift-JIS で保存されたファイルを UTF-8 で読み込もうとするとUnicodeDecodeErrorで止まり、その逆も同じく文字化けします。
世界標準はUTF-8で、新規に保存するファイルは UTF-8 で書くのが原則です。ブラウザ実行環境では裏側で UTF-8 に固定されているのでencoding=の指定がなくても動きますが、ローカルでスクリプトを書くときは付けるようにしましょう。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2f.readline()を繰り返し呼んで、ファイルの末尾に達したときに返ってくる値はどれですか?
Q3既に内容のあるdata/done.txtをwith open("data/done.txt", "w") as f:で開いたとき、既存の内容はどうなりますか?