sec05 - Chapter 5: Beginner’s Guide to Python Exception Handling with Practical Project Examples
スポンサーリンク

How to Implement Python Exception Handling in the Fortune Cookie Project

In this lecture, you will learn how to incorporate exception handling into a real project (Fortune Cookie). The key point is not to write many fine-grained exceptions everywhere, but instead to place one or two concise exception-handling blocks that safely cover possible failure points.

  • Add try-except to the actual programs (fortune.py and language_manager.py) to handle expected failures safely.
  • Show clear, user-friendly messages when errors occur.
  • Exception handling should be “limited to meaningful locations,” not applied everywhere just to catch all errors.

Common Types of Python Errors and Their Causes

When creating or executing modules and packages, the following three types of errors commonly occur:

  • ImportError
    • Raised when there is a problem loading a module via an import statement.
  • ModuleNotFoundError
    • A subclass of ImportError, raised when a module cannot be found by an import statement.
  • AttributeError
    • Raised when attempting to access a nonexistent attribute (variables, functions, etc.). Check that the target module correctly defines the variables and functions you are referencing.

Where to Write Exception Handling in Python (Key Points)

To make your program practically safe, you only need to focus on the following two points. In this lecture, we will place exception handling in these two areas.

  • The module import section
    • Prepare for cases where module files (e.g., message/language_jp.py) do not exist or cannot be loaded.
  • Data retrieval/execution section (function calls, etc.)
    • Prepare for cases where the module loads successfully, but its contents have unexpected formats that cause errors when accessed.

Scattering exception handling everywhere makes it harder to trace what happened. Therefore, it’s best practice to limit it to “critical boundaries” such as module loading, external I/O, and parsing user input.

Code Example Before Adding Python Exception Handling (Before)

First, let’s review the pre-modification state. Below are the fortune.py and manager/language_manager.py files used so far.

(Lines below if __name__ == '__main__': are omitted.)

# fortune.py
import random

import manager.language_manager as lang_manager


def main():
    # Choose language
    lang_code = input('Choose a language (jp/en): ')
    # Receive the language-specific module
    lang_module = lang_manager.get_language_module(lang_code)
    messages = lang_module.MESSAGES
    fortunes = lang_module.FORTUNES

    # Open a cookie?
    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):
    '''Return the module that matches the given language 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('Unsupported language code. Using Japanese instead.')
        from message import language_jp
        return language_jp
スポンサーリンク

Code Example After Implementing Python Exception Handling (After)

Safe Handling of Import Errors (ImportError / ModuleNotFoundError)

  • Protect the import section with try-except, and if it fails, display an error and exit the program.
# manager/language_manager.py
import sys

def get_language_module(lang_code):
    '''Return the module that matches the given language 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('Unsupported language code. Using Japanese instead.')
            from message import language_jp
            return language_jp
    except ImportError as e:
        print('ImportError: Required message module not found.')
        print('Details:', e)
        print('Exiting the program.')
        sys.exit(1)  # Exit with non-zero status (indicates abnormal exit)

Key points in the code:

  • [Line 2, 21] import sys, sys.exit(1)
    • The sys module is used to return an exit status. sys.exit(1) indicates an error exit.
    • Additional note: sys.exit() terminates Python and raises a SystemExit exception. When passing an integer, shells treat 0 as "normal exit" and non-zero integers as "abnormal exit." In all cases, Python terminates execution and raises SystemExit.
  • [Lines 6–16] try: ~
    • This block is wrapped in try because ImportError or ModuleNotFoundError may occur when a module cannot be found.
  • [Line 17] except ImportError as e:
    • Since ModuleNotFoundError is a subclass of ImportError, catching ImportError here covers both.
    • If ModuleNotFoundError occurs, it will also be caught by ImportError.
  • [Line 18 and onward]
    • Print user-friendly output explaining what happened, along with the technical details (e), then exit safely.

How to Handle Runtime Errors in main()

After obtaining the module, the processing that follows (such as retrieving messages) is wrapped with a general except to “display details → exit safely.”

# fortune.py
import random

import manager.language_manager as lang_manager


def main():
    try:
        # Choose language
        lang_code = input('Choose a language (jp/en): ')
        # Receive the language-specific module
        lang_module = lang_manager.get_language_module(lang_code)
        messages = lang_module.MESSAGES
        fortunes = lang_module.FORTUNES

        # Open a cookie?
        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:
        # Handle missing keys in the MESSAGES dict
        print('Internal error: Some message text is incorrectly configured.')
        print('Missing key:', e)
    except Exception as e:
        # Catch unexpected errors for diagnostics
        print('An unexpected error has occurred.')
        print('Details:', e)

Key points in the code:

  • [Line 8] try inside def main():
    • Wrapping the entire processing block prevents the program from stopping abruptly when unexpected errors occur.
  • [Line 24] except KeyError as e:
    • This individually handles cases where required keys are missing from the MESSAGES dict.
  • [Line 28] except Exception as e:
    • This is a final fallback. Use this message to inform the user and later connect the information to logs or bug reports if needed.

Basics of User Notification and Logging in Python Exception Handling

For beginners, print() is sufficient, but in real-world development you’ll use the logging module to record errors to files or external monitoring systems. Keep the following two principles in mind:

  • User-facing messages (print) should be concise: Tell the user “what happened” and “what to do next” briefly (e.g., “Error: Required module not found. Please contact an administrator.”).
  • Technical information (error details) should go to logs: Stack traces and exception objects should be logged. Reference: Python: logging

Checklist for Verifying Python Exception Handling Works Correctly

After writing exception-handling code, try intentionally causing errors to ensure your handlers behave as expected.

  • Test 1: Normal behavior — Run the program with all files present and confirm that the language messages display correctly (no errors should occur).
  • Test 2: Behavior when modules are missing — Temporarily rename message/language_jp.py and run the program to check whether the error message is displayed and execution stops.
  • Test 3: Behavior when message keys are missing — Intentionally remove a key from the MESSAGES dict inside a language module and check that KeyError is handled properly.

Summary: Practical Points for Python Exception Handling

This lecture introduced the minimum mechanisms required to ensure that “your program behaves clearly and safely for both users and operators when failures occur.”

  • Exception handling should be neither too sparse nor too excessive—limit it to critical locations such as imports, external I/O, and user input parsing.
  • In this lecture, we added simple exception handling to two areas: the import section and the execution section.
  • In the future, consider introducing logging to record detailed exception traces to files.
スポンサーリンク