例外を発生させる

完了

トレースバックと例外の処理について理解を深めたので、例外を発生させてみましょう。

コードを記述するときに、エラー状態が発生する可能性がある状況が既にわかっている場合があります。 そのような状況では、何が問題かを他のコードで認識できるようにする例外を発生させると便利です。

例外を発生させることは、他のコードの意思決定にも役立ちます。 前に説明した通り、エラーによっては、問題を解決するか、回避するか、または無視するか、コードで適切な決定を行うことができます。

宇宙飛行士は、水の使用量を 1 日あたり約 11 リットルに制限しています。 宇宙飛行士の数に応じて、1 日以上後に残る水の量を計算できる関数を作成します。

def water_left(astronauts, water_left, days_left):
    daily_usage = astronauts * 11
    total_usage = daily_usage * days_left
    total_water_left = water_left - total_usage
    return f"Total water left after {days_left} days is: {total_water_left} liters"

5 人の宇宙飛行士、残った水が 100 リットル、残り 2 日で試してみます。

water_left(5, 100, 2)
'Total water left after 2 days is: -10 liters'

これはあまり役に立ちません。マイナスのリットルはエラーであるべきです。 そうすれば、ナビゲーション システムは、2 日後には全員に十分な水が残っていないとことを宇宙飛行士に警告できます。 ナビゲーション システムをプログラミングしているエンジニアは、water_left() 関数で例外を発生させて、エラー状態を警告できます。

def water_left(astronauts, water_left, days_left):
    daily_usage = astronauts * 11
    total_usage = daily_usage * days_left
    total_water_left = water_left - total_usage
    if total_water_left < 0:
        raise RuntimeError(f"There is not enough water for {astronauts} astronauts after {days_left} days!")
    return f"Total water left after {days_left} days is: {total_water_left} liters"

もう一度実行します。

water_left(5, 100, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in water_left
RuntimeError: There is not enough water for 5 astronauts after 2 days!

ナビゲーション システムでは、警告を通知するコードで RuntimeError を使用して警告を出すことができます。

try:
    water_left(5, 100, 2)
except RuntimeError as err:
    alert_navigation_system(err)

water_left() 関数を更新して、サポートされていない型を渡すのを防ぐこともできます。 整数ではない引数を渡して、エラー出力を確認してみます。

water_left("3", "200", None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in water_left
TypeError: can't multiply sequence by non-int of type 'NoneType'

TypeError からのエラーは、この関数に何が必要かという観点からは、あまり親切なものではありません。 関数を更新して、TypeError を使用するが、よりメッセージを分かりやすくします。

def water_left(astronauts, water_left, days_left):
    for argument in [astronauts, water_left, days_left]:
        try:
            # If argument is an int, the following operation will work
            argument / 10
        except TypeError:
            # TypeError will be raised only if it isn't the right type 
            # Raise the same exception but with a better error message
            raise TypeError(f"All arguments must be of type int, but received: '{argument}'")
    daily_usage = astronauts * 11
    total_usage = daily_usage * days_left
    total_water_left = water_left - total_usage
    if total_water_left < 0:
        raise RuntimeError(f"There is not enough water for {astronauts} astronauts after {days_left} days!")
    return f"Total water left after {days_left} days is: {total_water_left} liters"

もう一度実行すると、分かりやすいエラーが表示されます。

water_left("3", "200", None)
Traceback (most recent call last):
  File "<stdin>", line 5, in water_left
TypeError: unsupported operand type(s) for /: 'str' and 'int'

During handling of the preceding exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in water_left
TypeError: All arguments must be of type int, but received: '3'