ファイルと例外
実用的な作業を行うプログラムのほとんどは、ファイルシステムに触れます。設定の読み込み、結果の書き出し、データの読み込みなどです。そして問題が起きると、Pythonは例外を発生させます。例外とは、予期しないことが起きたというシグナルです。この章では、その両方を扱います。ファイルからのデータの入出力と、クラッシュではなく適切にエラーを処理するコードの書き方です。
ファイルを開く
open()はファイルを開き、読み書きできるオブジェクトを返します。パスと、ファイルに対して何をしたいか(読み込み、書き込み、追記)を指定します。使い終わったら必ずファイルを閉じてください。with文を使えば、これを自動的に行ってくれます。
f = open("data.txt", "r") # "r" = 読み込み
content = f.read()
f.close()"r"はモードです:
| モード | 意味 |
|---|---|
"r" | 読み込み。ファイルが存在している必要があります。デフォルトモード。 |
"w" | 書き込み。ファイルを作成または上書きします。 |
"a" | 追記。消去せず末尾に追加します。 |
"x" | 作成。ファイルが既に存在する場合は失敗します。 |
"r+" | 読み書き両用。 |
"b" | バイナリ。任意のモードに追加: "rb", "wb"。 |
使い終わったら必ず.close()を呼んでください。忘れるとファイルがロックされたままになり、データの破損につながる可能性があります。これを確実に処理する方法がwith文です。
with文
with open(...)はファイルを管理してくれて、インデントされたブロックが終わるとき、たとえエラーが起きた場合でも自動的にファイルを閉じます。手動でopen()/close()を使うのではなく、常にwith open(...)を使ってください。より安全で、これが標準です。
with open("data.txt", "r") as f:
content = f.read()
# ここで f は確実に閉じられるwithは何をするのか?
withはPythonのコンテキストマネージャ構文です。セットアップとティアダウンのコードを代わりに実行します。この場合、ファイルを開き、確実に閉じます。内部の仕組みを知る必要はありません。open()と一緒に使えばよいだけです。
ファイルを読む
読み込みには3つの方法があります。.read()はファイル全体を1つの文字列としてロードします。.readline()は1行ずつ読み込みます。ファイルオブジェクト自体を直接イテレートすると、行ごとに読み込みます。これは大きなファイルでは最も効率的な方法です。すべてを一度にメモリにロードしないからです。
with open("data.txt", "r") as f:
content = f.read() # ファイル全体を1つの文字列として
with open("data.txt", "r") as f:
first_line = f.readline() # 1行ずつ
with open("data.txt", "r") as f:
lines = f.readlines() # 行のリスト、各行は "\n" で終わる大きなファイルの場合、1行ずつ読む方が一度に全部ロードするより効率的です:
with open("big_file.txt", "r") as f:
for line in f: # ファイルを直接イテレート、メモリ効率が良い
print(line.strip()) # strip() は末尾の改行を削除ファイルオブジェクトを直接イテレートする(for line in f)のは、大きなファイルを読むのに最も効率的で慣用的な方法です。
ファイルに書き込む
"w"モードはファイルが存在する場合、その内容を完全に上書きします。"a"モードは末尾に追加します。.write()は改行を自動的に追加しません。各行の末尾に明示的に"\n"を含めてください。複数行を一度に書き込むには、"\n".join()で結合します。
with open("output.txt", "w") as f:
f.write("Hello, world\n")
with open("output.txt", "a") as f:
f.write("Another line\n")"w"はファイルが存在する場合に完全に上書きします。"a"は末尾に追加します。
f.write()は改行を自動的に追加しないので、明示的に"\n"を含めてください。複数行を一度に書き込むには:
lines = ["Line one", "Line two", "Line three"]
with open("output.txt", "w") as f:
f.write("\n".join(lines) + "\n")例外
Pythonが処理できない問題に遭遇すると、例外を発生させます。これは何が起きたかとどこで起きたかを記述するエラーです。処理しなければ、プログラムはクラッシュし、トレースバックが表示されます。下の表は、よく遭遇する例外を示しています。
よく遭遇する例外:
| 例外 | 発生する状況 |
|---|---|
FileNotFoundError | open()がファイルを見つけられない |
ValueError | 関数が正しい型だが内容が間違った値を受け取った、例: int("abc") |
TypeError | 完全に型が間違っている、例: "hello" + 5 |
KeyError | 辞書のキーが存在しない |
IndexError | リストのインデックスが範囲外 |
ZeroDivisionError | ゼロ除算 |
AttributeError | オブジェクトがその属性やメソッドを持たない |
try / except
失敗する可能性のあるコードをtryブロックでラップしてください。例外が発生すると、対応するexceptブロックがそれを処理し、クラッシュを防ぎます。どの例外をキャッチするかは具体的に指定してください。except:だけですべてをキャッチすると、本当のバグが隠れてしまいます。
try:
value = int("abc")
except ValueError:
print("That's not a valid number")どの例外をキャッチするかは具体的に指定してください。except:だけですべての例外をキャッチするとバグが隠れます:
# 悪い例、プログラマのミスも含めてすべてキャッチする
try:
result = do_something()
except:
pass
# 良い例、期待していて実際に処理できるものだけをキャッチする
try:
result = do_something()
except FileNotFoundError:
print("File not found")複数の例外をキャッチする
異なるエラー型を別々のexceptブロックで処理することも、タプルを使って1つのブロックで複数の型をキャッチすることもできます。as eの部分でエラーメッセージにアクセスできます。
try:
data = int(user_input)
result = 100 / data
except ValueError:
print("Not a number")
except ZeroDivisionError:
print("Can't divide by zero")または、タプルで複数をキャッチします:
except (ValueError, ZeroDivisionError) as e:
print(f"Input error: {e}")as eは例外オブジェクトを名前にバインドし、メッセージを調べられるようにします。
elseとfinally
elseは例外が発生しなかった場合にのみ実行されます。finallyは例外があってもなくても常に実行されます。finallyは、何があっても実行する必要があるクリーンアップに有用です。
try:
with open("data.txt") as f:
content = f.read()
except FileNotFoundError:
print("File not found, using defaults")
content = ""
else:
print("File loaded successfully")
finally:
print("Done attempting to load file") # 常に実行されるfinallyは、ファイルに対して既にwithを使用していても、接続を閉じる、ロックを解放するなどのクリーンアップに最も有用です。
raise
raiseを使って自分で例外を発生させることもできます。これにより、関数が間違った値を静かに返すのではなく、呼び出し側に問題を明確に伝えることができます。
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / bこれにより関数が何を期待しているかが明示され、呼び出し側に問題が明確に伝わります。
カスタム例外クラス
大規模なプログラムでは、Exceptionを継承して独自の例外型を定義できます。これにより、呼び出し側はあなた固有のエラーを他の種類のエラーとは別にキャッチできます。
class InsufficientFundsError(Exception):
pass
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(
f"Cannot withdraw {amount}, balance is {self.balance}"
)
self.balance -= amounttry:
account.withdraw(1000)
except InsufficientFundsError as e:
print(f"Transaction declined: {e}")JSON
JSONはあらゆるものが扱う形式です。API、設定ファイル、データのエクスポートなど。Pythonのjsonモジュールがこれを直接処理します。json.load()はファイルからJSONを読み込んでPythonの辞書やリストにします。json.dump()はPythonの辞書やリストをJSONとしてファイルに書き込みます。
ファイルからJSONを読む:
import json
with open("config.json", "r") as f:
config = json.load(f) # JSON を Python の dict/list にパース
print(config["setting"])JSONをファイルに書く:
import json
data = {"name": "さくら", "score": 87, "active": True}
with open("output.json", "w") as f:
json.dump(data, f, indent=2) # indent= で人間が読みやすくなるJSONからPythonへの型のマッピング:
| JSON | Python |
|---|---|
object {} | dict |
array [] | list |
string "" | str |
| number | int または float |
true / false | True / False |
null | None |
ファイルを介さずにJSON文字列とPythonオブジェクトを変換するには:
import json
# 文字列から Python へ
data = json.loads('{"name": "さくら", "score": 87}')
# Python から文字列へ
text = json.dumps({"name": "さくら", "score": 87}, indent=2)json.load()はファイルオブジェクトから読みます。json.loads()("s"付き)は文字列から読みます。
実践
簡単なゲームのセーブ/ロードパターン。状態をJSONに書き出し、次回起動時に読み戻し、セーブファイルがまだない場合はデフォルトにフォールバックします:
import json
SAVE_FILE = "save_game.json"
def save_game(player_data: dict) -> None:
with open(SAVE_FILE, "w") as f:
json.dump(player_data, f, indent=2)
print("Game saved.")
def load_game() -> dict:
try:
with open(SAVE_FILE, "r") as f:
return json.load(f)
except FileNotFoundError:
print("No save file found, starting fresh.")
return {"name": "Player", "score": 0, "level": 1}
state = load_game()
state["score"] += 50
save_game(state)
