sec05 - 第5章:【初心者向け】Pythonの例外処理をプロジェクトに組み込む方法|importエラーと実行時エラーの対処
スポンサーリンク

Pythonの例外処理をFortune Cookieプロジェクトに実装する方法

このレクチャーでは、実際のプロジェクト(Fortune Cookie)に例外処理を組み込む方法を学びます。ポイントは、例外を細かく多く書くのではなく、起こり得る問題に対して安全に処理できる範囲で、1〜2か所に簡潔な例外処理を置くことです。

  • 実際のプログラム(fortune.py及びlanguage_manager.py)に try-except を導入して、想定される失敗を安全に扱えるようにする。
  • エラーが起きたときにユーザーにわかりやすく伝える簡潔なメッセージを表示する。
  • 例外処理は「全部を捕まえる」のではなく「意味のある箇所で限定的に適用する」ようにする。

Pythonでよく発生するエラーの種類と原因のチェックポイント

モジュールやパッケージの作成・実行時に発生する一般的なエラーは、次の3種類です。

  • ImportError
    • import 文でモジュールをロードしようとして問題が発生すると送出されます。
  • ModuleNotFoundError
    • ImportErrorのサブクラスで、import 文でモジュールが見つからない場合に送出されます。
  • AttributeError
    • 存在しない属性(変数や関数など)を参照しようとした場合に送出されます。対象モジュールに変数や関数が正しく定義されているか確認してください。

Pythonコードのどこに例外処理を書くべきか(重要ポイント)

実務的に安全なプログラムにするためには、次の2点だけ押さえれば十分です。今回はこれら2か所に例外処理を入れます。

  • モジュールの import 部分
    • モジュールファイル(例:message/language_jp.py)が存在しない、または読み込めない場合に備える。
  • データ取得/実行部(関数呼び出しなど)
    • モジュールは読み込めても、中身のデータが想定外の形式でアクセスに失敗する場合に備える。

例外処理をあちこちに散りばめると、かえって何が起きたのか追跡しづらくなります。ですので「重要な境界(モジュール読み込み、外部入出力、ユーザー入力のパース)」に絞るのがベストプラクティスです。

Python例外処理を追加する前のコード例(Before)

まずは改修前の状態を振り返ります。以下は、これまでの流れで使ってきたfortune.pymanager/language_manager.pyです。

(if __name__ == '__main__':行より下は省略)

# fortune.py
import random

import manager.language_manager as lang_manager


def main():
    # 言語を選択
    lang_code = input('言語を選んでください (jp/en): ')
    # 言語コードに対応したモジュールが返される
    lang_module = lang_manager.get_language_module(lang_code)
    messages = lang_module.MESSAGES
    fortunes = lang_module.FORTUNES

    # クッキーを開きますか?
    print(f"=== {messages['app_title']} ===")
    answer = input(messages['prompt_open'])

    if answer == 'y':
        print(f"{messages['fortune_prefix']} {random.choice(fortunes)}")
    else:
        print(messages['goodbye'])
# manager/language_manager.py

def get_language_module(lang_code):
    '''言語コードに応じてモジュールを返す'''
    if lang_code == 'en':
        from message import language_en
        return language_en
    elif lang_code == 'jp':
        from message import language_jp
        return language_jp
    else:
        print('対応していない言語コードです。日本語を使用します。')
        from message import language_jp
        return language_jp
スポンサーリンク

Python例外処理を実装したコード例(After)

importエラー(ImportError / ModuleNotFoundError)の安全な処理方法

  • モジュールのimport部分はtry-exceptで保護し、失敗したら「エラー表示→プログラム終了」する。
# manager/language_manager.py
import sys

def get_language_module(lang_code):
    '''言語コードに応じてモジュールを返す'''
    try:
        if lang_code == 'en':
            from message import language_en
            return language_en
        elif lang_code == 'jp':
            from message import language_jp
            return language_jp
        else:
            print('対応していない言語コードです。日本語を使用します。')
            from message import language_jp
            return language_jp
    except ImportError as e:
        print('ImportError: 必要なメッセージモジュールが見つかりません。')
        print('詳しい情報:', e)
        print('プログラムを終了します')
        sys.exit(1)  # 非ゼロで終了(異常終了を示す)

コードのポイント:

  • 【2行目・21行目】import sys,sys.exit(1)
    • sysモジュールは終了時にステータスコードを返すために使います。sys.exit(1)は「エラー終了」を示します。
    • 補足説明:sys.exit()はPython を終了させ、SystemExit例外を送出します。引数に整数を渡した場合、シェル等は 0 は "正常終了"、0 以外の整数を "異常終了" として扱います。どちらの場合も、Pythonの処理を終了させ、「SystemExit例外を送出する」ことは共通です。
  • 【6行目~16行目】try: ~
    • モジュールが見つからない時などに、ImportError/ModuleNotFoundErrorが発生するため、ここをtryで囲みます。
  • 【17行目】except ImportError as e:
    • ModuleNotFoundErrorImportErrorのサブクラスですので、ここではImportErrorでエラーを捕捉しています。
    • ModuleNotFoundErrorが発生した場合も、ImportErrorで捕捉することができます。
  • 【18行目以降】
    • ユーザー(または運用者)に何が起きたかがわかるよう、エラーの内容と技術的な詳細(e)を出力して安全に終了します。

実行時エラーをmainでまとめて処理する方法

モジュールの情報を受け取った後の処理(各言語のメッセージ取得など)は、汎用的なexceptで補足して「詳細を出力→安全に終了」する。

# fortune.py
import random

import manager.language_manager as lang_manager


def main():
    try:
    # 言語を選択
        lang_code = input('言語を選んでください (jp/en): ')
        # 言語コードに対応したモジュールが返される
        lang_module = lang_manager.get_language_module(lang_code)
        messages = lang_module.MESSAGES
        fortunes = lang_module.FORTUNES

        # クッキーを開きますか?
        print(f"=== {messages['app_title']} ===")
        answer = input(messages['prompt_open'])

        if answer == 'y':
            print(f"{messages['fortune_prefix']} {random.choice(fortunes)}")
        else:
            print(messages['goodbye'])
    except KeyError as e:
        # もしMESSAGESのdictに期待するキーが欠けていた場合の対処
        print('内部エラー: 設定されている文言に不備があります。')
        print('不足しているキー:', e)
    except Exception as e:
        # 想定外の例外はここで捕まえて情報を出しておく
        print('予期せぬエラーが発生しました。')
        print('詳細:', e)

コードのポイント:

  • 【8行目】def main():の中のtry
    • 実際の処理全体をtryで囲んでおくと、想定外のエラーが起きても、プログラムが即終了するのを防げます。
  • 【24行目】except KeyError as e:
    • 言語データのMESSAGESdictに、必要なキーが欠けている場合に備えて個別に捕捉し、エラー内容を出力します。
  • 【28行目】except Exception as e:
    • 最終的な保険です。ここでエラーメッセージを見て、必要ならログやバグ報告につなげます。

Pythonの例外処理におけるユーザー通知とログ記録の基本

初心者のうちはprint()で事足りますが、実務ではloggingモジュールを使って、エラーをファイルや外部の監視システムに記録します。ここでは2つの原則だけ押さえておきましょう。

  • ユーザー向けメッセージ(print)は簡潔に:ユーザーには「何が起きたか」「次に何をすればよいか」を短く提示します(例:「エラー: 必要なモジュールが見つかりません。管理者に連絡してください。」)。
  • 技術情報(例外の詳細)はログへ:スタックトレースや例外オブジェクトはログに残すのが基本です。参考: Python: logging

Python例外処理が正しく動くか確認するテスト項目

例外処理のコードを記述したら、一時的にエラーが出るようなコードに書き換えてみて、想定通りにエラーが補足されるか確認してみましょう。

  • テスト1:通常動作確認:全ファイルが揃った状態で実行し、言語メッセージが正しく表示されるか確認する。(何のエラーも出ないことを確認する。)
  • テスト2:モジュール欠落時の挙動:一時的にmessage/language_jp.pyのファイル名を変えて実行し、エラーメッセージが表示されて終了するか確認する。
  • テスト3:メッセージキー欠落の挙動:languageモジュール内で意図的にMESSAGESのキーを消して実行し、KeyErrorがハンドルされるか確認する。

Python例外処理のポイントまとめ(実務で役立つ基礎)

このレクチャーでは、「プログラムが起きうる失敗に対して、ユーザーと運用者の両方にとって分かりやすく・安全に振る舞う」ための最低限の仕組みを整えました。

  • 例外処理は「多すぎても少なすぎてもよくない」ので、重要箇所(import、外部IO、ユーザー入力)に絞るのが良い。
  • 今回は import 部分と実行部の2か所にシンプルな例外処理を追加した。
  • 将来的にはloggingを導入して、例外の詳細をファイルに残す運用へ進めると良いでしょう。
スポンサーリンク