ファイル入出力は身近で便利ですが、扱いを誤ると閉じ忘れや例外でのリソースリークにつながります。
Pythonのwith
構文は、ファイルを安全に開き、確実に閉じるための標準的な方法です。
本稿では、with構文でファイルを安全に扱う理由と、基本から実用例、初心者がつまずきやすいポイントまでを丁寧に解説します。
Pythonのwith構文とは(ファイルを安全に開く理由)
コンテキストマネージャで自動で閉じる
仕組みの概要
Pythonのwith
は「コンテキストマネージャ」という仕組みを使います。
ファイルオブジェクトはコンテキストマネージャとして振る舞い、with
ブロックに入るときに準備し、出るときに後片付けを行います。
この後片付けにファイルのclose()
が含まれるため、閉じ忘れが起きません。
実務的な利点
コードは短く読みやすくなり、レビューでも意図を理解しやすくなります。
可読性と安全性を同時に満たせるのがwith
の大きな魅力です。
例外時でも安全(後片付けが確実)
例外が発生しても自動でクリーンアップ
ブロック内で例外が発生しても、with
は必ず後片付けを実行します。
一方、with
を使わない場合、後片付けを確実にするにはtry ... finally
を自分で書く必要があります。
振る舞いの比較(概要)
観点 | with構文 | 手動でclose() |
---|---|---|
閉じ忘れの可能性 | なし | あり |
例外に対する強さ | 強い(確実に閉じる) | try … finallyが必要 |
コード量 | 短い | 長くなりがち |
可読性 | 高い | 低下しやすい |
open()とwith構文の関係
それぞれの役割
open()
はファイルを開いてファイルオブジェクトを返す関数です。
with
は、そのオブジェクトのライフサイクル(開始と終了)を管理します。
open()
がファイルを「用意」し、with
が「安全に使って片付ける」という関係です。
よくある誤解
with
はopen()
の代わりではありません。
with open(...) as f:
の形で併用するのが基本です。
with構文の基本(使い方と最小コード例)
with open() as f の基本形
最小のパターン
最小の使い方はwith open(..., mode, encoding) as f:
という形です。
以下はテキストを書き込む最小例です。
# 最小のwith構文の例: ファイルに1行書き込む
# ポイント:
# - mode="w" は新規作成または上書き
# - encoding="utf-8" を明示すると文字化けを防ぎやすい
with open("sample.txt", mode="w", encoding="utf-8") as f:
f.write("こんにちは、with構文!\n") # ファイルに1行書く
# withブロックを抜けると自動でclose()される
print("書き込み完了")
書き込み完了
ブロックの範囲=ファイルの有効範囲
ブロックを出ると自動で閉じられる
ファイルが有効に使えるのはwith
ブロックの内部だけと考えましょう。
ブロックを出ると自動的に閉じられます。
# デモ用: ファイルを用意
with open("sample.txt", "w", encoding="utf-8") as f:
f.write("line\n")
# withの内外で f.closed の値を比較
with open("sample.txt", "r", encoding="utf-8") as f:
print("inside closed? ->", f.closed) # False (開いている)
# ここでは f はスコープに残るが、既に閉じられている
print("outside closed? ->", f.closed) # True (閉じている)
inside closed? -> False
outside closed? -> True
close()は不要
try … finallyを書かなくてよい
with構文を使えばclose()
を明示的に呼ぶ必要はありません。
同等の安全性を手動で確保するにはtry ... finally
が必要です。
# 手動で安全に閉じる(推奨しないが等価な書き方)
f = open("sample.txt", "r", encoding="utf-8")
try:
text = f.read()
print("手動:", len(text), "文字")
finally:
f.close() # ここを忘れると危険
# withで簡潔かつ安全に書く(推奨)
with open("sample.txt", "r", encoding="utf-8") as f:
text = f.read()
print("with:", len(text), "文字")
手動: 5 文字
with: 5 文字
目的別のwith構文例(ファイル操作の基本)
テキストを読む(read)
全文を一度に読み込む
f.read()
はファイルの全文を文字列として返します。
小さめのファイルに向いています。
# デモ用: 読み取り対象のファイルを作成
with open("read_sample.txt", "w", encoding="utf-8") as f:
f.write("1行目\n2行目\n3行目\n")
# with構文で全文を読む
with open("read_sample.txt", "r", encoding="utf-8") as f:
text = f.read() # ファイル全体を取得
print(text, end="") # 末尾の改行を活かすためend=""に
1行目
2行目
3行目
行ごとに読む
for line in f
で行単位に読み取れます。
メモリ効率がよく、大きなファイルにも向きます。
with open("read_sample.txt", "r", encoding="utf-8") as f:
for idx, line in enumerate(f, start=1):
print(f"{idx}:", line.strip())
1: 1行目
2: 2行目
3: 3行目
テキストに書く(write)
1行ずつ追記または上書き
write()
で文字列を書き込みます。
mode=”w”は既存ファイルを上書きし、mode="a"
は末尾に追記します。
lines = ["alpha", "beta", "gamma"]
# 上書き作成
with open("write_sample.txt", "w", encoding="utf-8") as f:
for line in lines:
f.write(line + "\n") # 改行は手動で付ける
print("書き込み完了: write_sample.txt")
書き込み完了: write_sample.txt
複数ファイルを同時に開く(カンマ区切り)
2つ以上を一度に管理
カンマで区切って複数のファイルを同時に開くことができます。
両方ともブロック終了時に自動で閉じられます。
# デモ用: コピー元を用意
with open("source.txt", "w", encoding="utf-8") as s:
s.write("copy me\nline2\n")
# 2つのファイルを同時に開き、行単位でコピー
with open("source.txt", "r", encoding="utf-8") as src, \
open("dest.txt", "w", encoding="utf-8") as dst:
for line in src:
dst.write(line)
print("コピー完了: dest.txt に書き出しました")
コピー完了: dest.txt に書き出しました
例外を補足する(try exceptと併用)
openの失敗を捕捉する
ファイルが存在しない場合など、open()
自体が例外を送出します。
このときはtry
の中でwith open(...)
を書きます。
filename = "no_such_file.txt"
try:
with open(filename, "r", encoding="utf-8") as f:
data = f.read()
print(data)
except FileNotFoundError as e:
print("ファイルが見つかりません:", e.filename)
ファイルが見つかりません: no_such_file.txt
読み取り中の失敗を捕捉する
with
に入った後の処理で起こる例外は、ブロックの内側で捕捉します。
どちらの場合もファイルは必ず閉じられます。
# デモ用: 不正な変換を起こすケースを演出
with open("numbers.txt", "w", encoding="utf-8") as f:
f.write("10\n20\nnot-a-number\n30\n")
# 行を整数に変換。失敗したら行番号付きで通知
try:
with open("numbers.txt", "r", encoding="utf-8") as f:
for i, line in enumerate(f, start=1):
value = int(line.strip()) # ここでValueErrorになる可能性
print("OK:", value)
except ValueError as e:
print("整数に変換できない行があります:", e)
OK: 10
OK: 20
整数に変換できない行があります: invalid literal for int() with base 10: 'not-a-number'
安全に使うコツ(初心者向けベストプラクティス)
encodingを明示する(utf-8)
文字化けを防ぐ基本設定
常にencoding="utf-8"
を明示するのがおすすめです。
OSや環境によってはデフォルトのエンコーディングがUTF-8ではないことがあり、文字化けの原因になります。
# UTF-8で書いてUTF-8で読む
with open("utf8_sample.txt", "w", encoding="utf-8") as f:
f.write("あいうえお\n")
with open("utf8_sample.txt", "r", encoding="utf-8") as f:
print("encoding:", f.encoding)
print("content:", f.read().strip())
encoding: UTF-8
content: あいうえお
パス操作はpathlibを使う(Path)
文字列連結より安全で可読
pathlib.Path
を使うと、OS差異を気にせずパス結合や作成ができます。
パスのバグを減らし、テストもしやすくなります。
from pathlib import Path
data_dir = Path("data")
data_dir.mkdir(exist_ok=True) # フォルダがなければ作成
file_path = data_dir / "notes.txt" # OSに依存しない結合
with file_path.open("w", encoding="utf-8") as f:
f.write("pathlibで書いたテキスト\n")
print("作成したファイル:", file_path) # data/notes.txt など
作成したファイル: data/notes.txt
大きなファイルは逐次処理(iterで行単位)
メモリ効率よく処理する
大きなファイルではread()
で全読み込みせず、for line in f
で逐次処理しましょう。
必要な部分だけ読むのが基本です。
# デモ用: 行数のあるファイルを用意
with open("big.txt", "w", encoding="utf-8") as f:
for i in range(1, 6):
f.write(f"{i}行目\n")
# 先頭3行だけプレビュー
with open("big.txt", "r", encoding="utf-8") as f:
for i, line in enumerate(f, start=1):
if i > 3:
break
print(line.strip())
1行目
2行目
3行目
バイナリファイルでも使える(mode b)
画像や実行ファイルはバイナリモード
テキスト以外はrb
やwb
で開きます。
改行変換や文字コード処理を避け、バイト列をそのまま扱えます。
# PNGのシグネチャ(先頭8バイト)を例にバイナリで読み書き
png_sig = bytes.fromhex("89504E470D0A1A0A")
# 書き込みはwb(バイナリ書き込み)
with open("sig.bin", "wb") as f:
f.write(png_sig)
# 読み込みはrb(バイナリ読み込み)
with open("sig.bin", "rb") as f:
head = f.read(8) # 先頭8バイト
print("hex:", head.hex())
hex: 89504e470d0a1a0a
まとめ
Pythonでファイルを安全に扱う最も簡単で確実な方法はwith open(...) as f:
を使うことです。
これにより、例外の有無に関わらず自動でclose()
が呼ばれ、閉じ忘れやリソースリークを防げます。
初心者の方は次の3点をまず徹底すると良いです。
1つ目はencoding="utf-8"
を常に明示すること、2つ目はpathlib.Path
でパスを扱うこと、3つ目は大きなファイルをfor line in f
で逐次処理することです。
さらに複数ファイルの同時オープンやtry ... except
との併用も、基本のパターンに沿えば自然に書けます。
日々のコードでwith
を習慣化し、シンプルかつ堅牢なファイル操作を身につけていきましょう。