Q1with open("data/notes.txt", "r") as f: の第 2 引数 "r" は何を表しますか?
ファイル入出力 — with open() で安全に読み書きする
Python のファイル入出力を基礎から解説します。with open() による自動クローズの仕組み、read / readlines / readline の使い分け、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: で開いたとき、既存の内容はどうなりますか?