Pythonの例外処理

PythonBeginner

はじめに

コンピュータプログラミングにおいて、例外処理(Exception Handling)とは、プログラム実行中に発生する「例外」と呼ばれる異常事態や予期せぬ条件に対して、適切に応答するためのプロセスです。Pythonには、エラーが発生した際に送出される組み込みの例外が多数用意されています。また、外部ライブラリを使用する場合も、そのライブラリ固有のカスタム例外を適切に処理する必要があります。

この実験では、Pythonにおける例外処理の基礎を学びます。まずは基本的な try および except ブロックから始め、複数の例外を処理する方法、finally ブロックの役割を理解し、最終的に独自のカスタム例外を作成・管理する方法を習得します。

基本的な例外処理

このステップでは、try および except ブロックを使用したPythonの例外処理の基礎を学びます。

数学的に、ゼロで割ることはできません。Pythonでこれを行うと、インタプリタは組み込みの ZeroDivisionError を送出します。実際に確認してみましょう。

注意: WebIDE(VS Codeのような環境)の使用に慣れていない場合は、まずこちらの実験で学習してください。

まず、左側のファイルエクスプローラーから basic_handler.py を開き、以下のコードを追加します。これは単純な除算を行う関数です。

def divide(dividend , divisor):
    print(dividend / divisor)

divide(dividend=10, divisor=5)

次に、ターミナルからスクリプトを実行します。

python3 basic_handler.py

正しい出力が表示されます。

2.0

次に、ゼロで割ろうとした場合に何が起こるかを確認します。basic_handler.py の最後の行を修正し、divide 関数を 0 で呼び出すように変更します。

def divide(dividend , divisor):
    print(dividend / divisor)

divide(dividend=10, divisor=0)

再度スクリプトを実行します。

python3 basic_handler.py

今度はプログラムがクラッシュし、ZeroDivisionError を示すトレースバックが表示されます。

Traceback (most recent call last):
  File "/home/labex/project/basic_handler.py", line 4, in <module>
    divide(dividend=10, divisor=0)
  File "/home/labex/project/basic_handler.py", line 2, in divide
    print(dividend / divisor)
ZeroDivisionError: division by zero

プログラムのクラッシュを防ぎ、ユーザーフレンドリーなメッセージを表示するために、try と except を使って例外を「処理」します。エラーが発生する可能性のあるコードを try ブロック内に配置し、エラーが発生した際に実行するコードを except ブロック内に配置します。

basic_handler.py を以下のように更新し、ZeroDivisionError を適切に処理するようにします。

## try-except: 例外を適切に処理する
def divide(dividend , divisor):
    try:  ## このコードの実行を試みる
        print(dividend / divisor)
    except ZeroDivisionError:  ## 特定の例外タイプをキャッチする
        print('You can not divide by 0')

divide(dividend=10, divisor=0)

もう一度スクリプトを実行します。

python3 basic_handler.py

プログラムはクラッシュせず、定義したメッセージが表示され、正常に終了します。

You can not divide by 0

1つの例外ブロックで複数の例外を処理する

このステップでは、単一の except ブロック内で複数の異なる例外タイプを処理する方法を学びます。これは、複数のエラーが発生し得る場合にエラー処理コードを簡潔にするために役立ちます。

except ブロックで (ZeroDivisionError, TypeError) のようなタプルを指定することで、それらを同じロジックで処理できます。

multiple_handler.py ファイルを開き、以下のコードを追加します。この関数は、TypeError(文字列と整数を連結しようとした場合)または ZeroDivisionError を発生させるように設計されています。

## 1つのexceptブロックで複数の例外を処理する
def divide(dividend , divisor):
    try:
        if (dividend == 10):
          var = 'str' + 1  ## これによりTypeErrorが発生する
        else:
          print(dividend / divisor)
    except (ZeroDivisionError, TypeError) as error:  ## 複数の例外タイプをキャッチする
        print(error)  ## エラーメッセージを表示する

divide(dividend=20, divisor=5)

まず、例外が発生しないシナリオでコードを実行してみましょう。

python3 multiple_handler.py

除算の結果が出力されます。

4.0

次に、TypeError を発生させてみます。multiple_handler.py の最後の行を修正し、被除数を 10 に変更します。

## ... (コードの残りはそのまま) ...
divide(dividend=10, divisor=5)

再度スクリプトを実行します。

python3 multiple_handler.py

except ブロックが TypeError をキャッチし、関連するエラーメッセージを表示します。

can only concatenate str (not "int") to str

最後に、ZeroDivisionError を発生させてみます。最後の行を再度修正し、ゼロで割るようにします。なお、TypeError の条件 (dividend == 10) は満たされないようにします。

## ... (コードの残りはそのまま) ...
divide(dividend=20, divisor=0)

スクリプトを実行します。

python3 multiple_handler.py

同じ except ブロックが今度は ZeroDivisionError をキャッチし、そのメッセージを表示します。

division by zero

検証を通過させるため、スクリプトの最後の呼び出しが TypeError を発生させる divide(dividend=10, divisor=5) になっていることを確認してください。

例外処理におけるfinallyコード

このステップでは、finally ブロックの目的と使い方を理解します。finally 句内のコードは、例外が発生したかどうかにかかわらず、常に実行されます。そのため、ファイルやネットワーク接続のクローズといったクリーンアップ処理に最適です。

どのように動作するか見てみましょう。finally_handler.py ファイルを開き、以下のコードを追加します。

## finallyブロック: 例外の有無にかかわらず常に実行される
def divide(dividend , divisor):
    try:
        print(dividend / divisor)
    except ZeroDivisionError:
        print('You can not divide by 0')
    finally:  ## 例外が発生しても常に実行される
        print('Execution finished')

divide(dividend=10, divisor=5)

ターミナルからスクリプトを実行します。

python3 finally_handler.py

この場合、例外は発生しません。try ブロックが正常に完了し、その後 finally ブロックが実行されます。

2.0
Execution finished

次に、エラーを発生させてみます。finally_handler.py の最後の行を修正し、ゼロで割るようにします。

## ... (コードの残りはそのまま) ...
divide(dividend=10, divisor=0)

再度スクリプトを実行します。

python3 finally_handler.py

今度は ZeroDivisionError が発生し、except ブロックでキャッチされます。例外が発生したにもかかわらず、except ブロックの終了後に finally ブロックが実行されます。

You can not divide by 0
Execution finished

ご覧の通り、どちらのシナリオでも "Execution finished" というメッセージが表示されます。これは、finally ブロックが常に実行されるべきコードにとって信頼できる場所であることを示しています。

カスタム例外

このステップでは、独自のカスタム例外を作成する方法を学びます。これは、プログラム固有の問題を記述する特定のエラータイプを作成できるため、アプリケーションの可読性と保守性を高める強力な機能です。

カスタム例外は、Pythonの基底クラスである Exception クラスを継承する新しいクラスを作成することで定義します。その後、raise キーワードを使用してこの例外を送出できます。

custom_exception.py ファイルを開き、以下のコードを追加して単純なカスタム例外を定義し、送出してみます。

## カスタム例外: Exceptionクラスを継承して作成する
class MyCustomException(Exception):
    pass

raise MyCustomException  ## カスタム例外を送出する

このスクリプトを実行すると、直ちに停止し、新しい例外のトレースバックが表示されます。

python3 custom_exception.py
Traceback (most recent call last):
  File "/home/labex/project/custom_exception.py", line 5, in <module>
    raise MyCustomException  ## カスタム例外を送出する
__main__.MyCustomException

カスタム例外を送出する際に、説明メッセージを提供することもできます。custom_exception.py を修正し、例外に文字列を渡すようにします。

class MyCustomException(Exception):
    pass

raise MyCustomException('A custom message for my custom exception')

これを実行すると、トレースバックにメッセージが表示されます。

Traceback (most recent call last):
  File "/home/labex/project/custom_exception.py", line 4, in <module>
    raise MyCustomException('A custom message for my custom exception')
__main__.MyCustomException: A custom message for my custom exception

カスタム例外の処理は、組み込みの例外と同じように try...except ブロックを使用します。custom_exception.py を最終的な形に更新し、カスタム例外を送出して処理するようにします。

class MyCustomException(Exception):
    pass

try:
    raise MyCustomException('A custom message for my custom exception')
except MyCustomException:
    print('My custom exception was raised')

最後にスクリプトを実行します。

python3 custom_exception.py

プログラムはクラッシュせず、except ブロック内のコードが実行され、確認メッセージが表示されます。

My custom exception was raised

まとめ

この実験では、Pythonにおける例外処理の重要なテクニックを学びました。まずは基本的な try および except ブロックを使用して特定のエラーをキャッチし、プログラムのクラッシュを防ぐ方法を学びました。次に、単一の except ブロックで複数の例外タイプを効率的に処理する方法を習得しました。また、常に実行されるべきクリーンアップコードに不可欠な finally ブロックについても学びました。最後に、独自のカスタム例外を作成、送出、処理する能力を身につけ、アプリケーションにおいてより堅牢で記述的なエラーハンドリングロジックを構築できるようになりました。