Python 入門

Python ファイルの例外処理

Pythonでファイルを扱う際、ファイルのオープンとクローズは基礎中の基礎です。しかし、これらの操作やデータの読み書きを行う過程で、エラーに遭遇することがあります。これらのエラーは、ファイルが存在しない、アクセス権限がない、あるいは他のプログラムがファイルを使用中であるといった、様々な要因によって引き起こされます。

これらの潜在的なエラーをエレガントに処理し、プログラムのクラッシュを防止するために、Pythonでは tryexcept ブロックを使用します。本章では、ファイル操作における効果的なエラーハンドリングの手法を網羅的に解説します。

1. try と except ブロックの理解

Pythonにおける tryexcept ブロックは、例外(Exceptions)、すなわちプログラム実行中に発生するエラーを処理するために使用されます。

基本的な構造は、例外が発生する可能性のあるコードを try ブロックの中に配置します。もし try ブロック内で例外が発生した場合、プログラムは即座に対応する例外タイプの except ブロックへとジャンプします。例外が発生しなかった場合は、except ブロックはスキップされます。

1.1 基本構文 (Syntax)

tryexcept ブロックの基本構造は以下の通りです。

try:
    # 例外が発生する可能性のあるコード
    # 例:ファイル操作など
except SomeExceptionType: # 具体的な例外タイプに置き換えます
    # 例外を処理するコード
    # 例:エラーメッセージの表示や補修措置

1.2 実行例:FileNotFoundError の処理

ファイル操作において最も一般的なエラーの一つが FileNotFoundError(ファイル未検出エラー)です。これは存在しないファイルをオープンしようとしたときに発生します。

try:
    file = open("nonexistent_file.txt", "r") # 存在しないファイルをオープンしようとする
    content = file.read()
    print(content)
    file.close()
except FileNotFoundError:
    print("エラー:ファイル 'nonexistent_file.txt' が見つかりませんでした。")
except Exception as e:
    print(f"予期せぬエラーが発生しました:{e}") # 汎用的な例外処理

この例では、ファイルが存在しない場合に open() 関数が FileNotFoundError をスローし、プログラムは except FileNotFoundError: ブロックを実行して明確なエラーメッセージを表示します。それ以外のエラーが発生した場合は、2番目の except ブロック(Exception as e)がキャッチします。

2. 複数の例外(Exception)の処理

複数の except ブロックを含めることで、単一の try 構造の中で多種多様な例外を処理できます。各 except ブロックは特定のタイプのエラーをキャッチするように設計できます。

try:
    file = open("my_file.txt", "r")
    content = file.read()
    number = int(content) # 内容を整数に変換(ValueError の可能性)
    result = 10 / number  # 数値で除算(ZeroDivisionError の可能性)
    print(result)
    file.close()
except FileNotFoundError:
    print("エラー:ファイルが見つかりません。")
except ValueError:
    print("エラー:ファイル内のデータが無効です。数値に変換できません。")
except ZeroDivisionError:
    print("エラー:ゼロで除算することはできません。")
except Exception as e:
    print(f"予期せぬエラーが発生しました:{e}")

この例の動作:

  • ファイルが存在しない場合は FileNotFoundError を処理。
  • 内容が整数に変換できない場合は ValueError を処理。
  • 変換後の数値がゼロの場合は ZeroDivisionError を処理。
  • 最後の except ブロックで、それ以外の想定外の例外をキャッチ。

3. else ブロック

すべての except ブロックの後に else ブロックを追加することもできます。else ブロック内のコードは、try ブロック内で例外が発生しなかった場合のみ実行されます。

try:
    file = open("my_file.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("エラー:ファイルが見つかりません。")
else:
    print("ファイルのオープンに成功しました。")
    file.close()

ここでは、ファイルが正常にオープンされた(例外が発生しなかった)場合のみ、「ファイルのオープンに成功しました。」と表示され、クローズ操作が行われます。

4. finally ブロック

finally ブロックは、例外の発生有無にかかわらず、必ず実行しなければならないコードを指定するために使用されます。これは通常、ファイルの強制的なクローズなど、リソースのクリーンアップに利用されます。

file = None  # try ブロックの外で変数 file を初期化
try:
    file = open("my_file.txt", "r")
    content = file.read()
    print(content)
except FileNotFoundError:
    print("エラー:ファイルが見つかりません。")
finally:
    if file:
        file.close()
        print("ファイルをクローズしました。")
    else:
        print("ファイルはオープンされませんでした。")

この例では、finally ブロックによってファイルが正常に開かれた場合でも、FileNotFoundError に遭遇した場合でも、確実に事後処理が行われます。変数を try 外で None 初期化することで、finally ブロックから安全にアクセスできるようにしています。

5. 実践ケースとデモンストレーション

5.1 ケース 1:エラー処理を伴うファイル書き込み

書き込み権限の不足やディスク容量不足など、ファイル書き込み時に発生しうるエラーを処理する例です。

try:
    file = open("output.txt", "w")
    file.write("ファイルに書き込むデータです。\n")
    file.write("もう一行のデータ。\n")
except IOError as e:
    print(f"エラー:ファイルに書き込めません。{e}")
finally:
    if file:
        file.close()
        print("ファイルをクローズしました。")

ここでは、入力/出力エラーを包括する IOError をキャッチしています。権限エラーなどが起きた際も、finally によってリソースが解放されます。

5.2 ケース 2:ファイルの読み込みとデータ処理

ファイルから数値を読み取って合計を計算するシナリオです。FileNotFoundError と、非数値データが含まれる場合の ValueError の両方に対処します。

def calculate_sum_from_file(filename):
    total = 0
    try:
        file = open(filename, "r")
        for line in file:
            try:
                number = float(line.strip()) # 各行を浮動小数点数に変換
                total += number
            except ValueError:
                print(f"警告:無効なデータをスキップします:{line.strip()}")
        return total
    except FileNotFoundError:
        print(f"エラー:ファイル '{filename}' が見つかりませんでした。")
        return None
    finally:
        if 'file' in locals() and file:  # file が定義されオープンされているか確認
            file.close()
            print("ファイルをクローズしました。")

# 実行例
filename = "numbers.txt"
with open(filename, "w") as f: # テスト用データの作成
    f.write("1\n")
    f.write("2\n")
    f.write("abc\n") # 意図的な無効データ
    f.write("4\n")

sum_of_numbers = calculate_sum_from_file(filename)
if sum_of_numbers is not None:
    print(f"ファイル内の数値の合計:{sum_of_numbers}")

内部の try...exceptValueError を処理することで、一部に無効なデータがあっても処理を中断せず、残りのデータを継続して処理できる柔軟な設計になっています。

5.3 ケース 3:with ステートメントによるリソースの自動管理

Python の with ステートメントを使用すると、例外が発生した場合でもリソースが自動的にクローズされるため、明示的な try...finally を書く必要がなくなり、非常にスマートです。

def read_file_content(filename):
    try:
        with open(filename, 'r') as file:
            content = file.read()
            return content
    except FileNotFoundError:
        print(f"エラー:ファイル '{filename}' が見つかりませんでした。")
        return None
    except IOError as e:
        print(f"ファイルの読み込み中にエラーが発生しました:{e}")
        return None

# 実行例
filename = "example.txt"
with open(filename, "w") as f:
    f.write("こんにちは、世界!\n")

content = read_file_content(filename)
if content:
    print("ファイルの内容:\n", content)

with open(...) as file: を使うことで、ブロックを抜ける際に自動的にクローズ処理が走ります。これによりコードがシンプルになり、リソースリーク(解放漏れ)のリスクを最小限に抑えることができます。