第12章 エラー処理と例外

基本的なエラーの種類

Pythonでプログラミングを行う際、さまざまなエラーが発生する可能性があります。これらのエラーは、その性質や原因に応じていくつかのカテゴリーに分類されます。以下はPythonにおける「基本的なエラーの種類」についての説明です。

構文エラー(SyntaxError)

構文エラーは、Pythonのコードが正しくない構文で書かれているときに発生します。これはプログラマがコードを書く際のタイポや、Pythonの文法を正しく理解していない場合によく見られます。

# 不正な構文の例
if x = 10:
    print("x is 10")

このコードでは、= は代入を意味するので、比較としては == を使用する必要があります。


実行エラー(RuntimeError)

実行エラーは、コードの構文自体は正しいが、実行時に何らかの問題が生じた場合に発生します。この種のエラーには多くのサブタイプがあります。以下はその一部です。

ValueError: 関数に不正な引数が渡されたときなどに発生します。

int("abc")  # 数値に変換できない文字列をint関数に渡す

TypeError: 操作が適用されたオブジェクトの型が不正な場合に発生します。

"Hello" + 5  # 文字列と整数は直接結合できません

IndexError: リストなどのシーケンス型のインデックスが範囲外の場合に発生します。

my_list = [1, 2, 3]
print(my_list[3])  # インデックスは0から始まるため、このコードはエラーになります

このコードでは、= は代入を意味するので、比較としては == を使用する必要があります。


論理エラー

論理エラーは、コードが期待される動作をしない場合に発生します。この種のエラーは、構文エラーや実行エラーとは異なり、Pythonのエラーメッセージとしては表示されません。プログラマがコードのロジックを誤って解釈した場合、または仕様を誤解した場合に発生します。

def divide(a, b):
    return a + b  # ここは a / b とすべきです

result = divide(10, 2)
print(result)  # 期待する出力は 5 ですが、12 が出力されます

これらのエラーを効果的に取り扱い、問題を診断・解決するためには、エラーメッセージを適切に読む能力とデバッグ技術が必要です。


例外の概念

例外は、プログラムの実行中に発生する予期しない状況やエラーを表現する仕組みです。これにより、エラーが発生した際の適切な処置やリカバリが可能となります。Pythonにおける例外の概念は、多くの高級言語と同様の考え方に基づいています。

例外の発生

Pythonで何らかのエラーが発生すると、そのエラーに関連した「例外オブジェクト」が生成され、所謂「例外が発生する」(または「例外がスローされる」)と言います。例外が発生すると、通常のプログラムのフローが中断され、例外を捕捉・処理する部分(例外ハンドラ)までジャンプします。

result = 1 / 0  # 0での除算はエラー(ZeroDivisionError例外)を引き起こします。


例外の捕捉

例外が発生したとき、それを捕捉して処理するためにtry...exceptブロックを使用します。tryブロックの中のコードが実行され、もし例外が発生すれば、関連するexceptブロックが実行されます。

try:
    result = 1 / 0
except ZeroDivisionError:
    print("0で割ることはできません。")

このコードは、「0で割ることはできません。」というメッセージを表示します。


複数の例外の捕捉

複数の例外を捕捉するためには、複数のexceptブロックを使用できます。また、すべての例外を捕捉するexceptブロックを定義することもできます。

try:
    # サンプルコード(例外を引き起こす可能性があるコード)
    num = int("abc")  # ValueErrorを引き起こす
    result = 1 / num
except ZeroDivisionError:
    print("0で割ることはできません。")
except ValueError:
    print("数値に変換できません。")


例外の伝播

例外は、捕捉されない場合、上位の関数やメインプログラムまで伝播します。最上位まで伝播し、どこにも捕捉されなかった場合、プログラムは終了します。


例外の再発生

raiseステートメントを使用すると、例外を再び発生させることができます。これは、特定の例外を捕捉した後に、その例外を上位のハンドラに伝播させたい場合などに役立ちます。

try:
    # 何らかのコード
    raise ValueError("これはサンプルのエラーメッセージです")
except ValueError as e:
    print(f"エラーが発生しました: {e}")
    raise  # 例外を再発生させる

例外は、エラー処理を中央集権的に行い、コードの可読性や保守性を向上させる上で非常に有用です。Pythonは多くの組み込み例外を持っており、独自の例外を定義することも可能です。


try-except文

Pythonのtry-except文は、例外処理のための主要な構造です。これにより、エラーが発生した際にプログラムがクラッシュするのを防ぎ、エラーを適切に処理することができます。

基本的な使い方

tryブロックの中には、エラーが発生する可能性のあるコードを配置します。もしtryブロック内で例外が発生すると、その例外に対応するexceptブロックが実行されます。

try:
    result = 1 / 0
except ZeroDivisionError:
    print("0での除算はできません。")

この例では、ZeroDivisionErrorが発生すると、エラーメッセージが表示されます。


複数の例外の取り扱い

1つのtryブロックに対して複数のexceptブロックを持つことができます。これにより、異なる種類の例外を異なる方法で処理することができます。

try:
    # 何らかの処理
    num = int("abc")
    result = 1 / num
except ZeroDivisionError:
    print("0での除算はできません。")
except ValueError:
    print("数値に変換できません。")


例外の詳細情報の取得

例外オブジェクトを取得することで、発生した例外の詳細な情報を得ることができます。

try:
    num = int("abc")
except ValueError as e:
    print(f"エラーが発生しました: {e}")


elseブロック

elseブロックは、tryブロック内のコードが例外を発生させずに正常に実行された場合に実行されます。

try:
    num = int("123")
except ValueError:
    print("数値に変換できません。")
else:
    print(f"変換した数値は{num}です。")


finallyブロック

finallyブロックは、tryブロック後で常に実行されるブロックです。これは、例外の有無に関係なく、リソースのクリーンアップなどの処理を保証するために使用されます。

try:
    f = open("somefile.txt", "r")
    content = f.read()
except FileNotFoundError:
    print("ファイルが見つかりません。")
finally:
    f.close()

この例では、finallyブロックがファイルを確実に閉じることを保証しています。


例外の発生

Pythonで例外を明示的に発生させることは、特定の状況下でのエラー処理やカスタム例外の通知に役立ちます。この目的で、raise文が提供されています。

基本的な例外の発生

raiseを使用して既存の例外を明示的に発生させることができます。

if x < 0:
    raise ValueError("xは正の数でなければなりません。")

この例では、xが0未満の場合、ValueErrorが発生します。


例外に情報を追加

例外を発生させる際に、追加の情報やメッセージを含めることができます。

raise ValueError(f"{x}は正の数でなければなりません。")


カスタム例外の定義

特定のアプリケーションやモジュール専用の例外を定義することも可能です。この場合、Exceptionクラスを継承して新しい例外クラスを作成します。

class CustomError(Exception):
    pass

# 何らかの条件に基づいてカスタム例外を発生させる
if some_condition:
    raise CustomError("カスタム例外が発生しました。")


他の例外を基にして新しい例外を発生

例外が発生した場合に、その例外を捕捉して新しいタイプの例外を発生させることもできます。

try:
    # 何らかの操作
except SomeError as e:
    raise CustomError("新しいメッセージ") from e

この方法では、新しい例外は元の例外に関連付けられます。この関連性は、トレースバックを調査する際に有用です。


強制的な例外の発生

assert文を使用して、条件がFalseの場合にAssertionErrorを発生させることもできます。

x = -1
assert x >= 0, "xは非負の数でなければなりません。"

この例では、xが0未満の場合、AssertionErrorが発生します。


例外とスタックトレース

Pythonの例外が発生すると、プログラムの通常の実行が中断され、エラーメッセージと一緒にスタックトレースが表示されます。このスタックトレースは、エラーが発生した箇所と、そのエラーへと繋がるコードの呼び出し順序を示す情報です。この情報は、エラーの原因を追跡して解決する上で非常に役立ちます。

スタックトレースの概要
  • スタックトレースは、エラーが発生したときのプログラムの呼び出し履歴を示すものです。
  • 通常、最新の呼び出し(エラーが発生した場所)がトレースの一番下に表示され、最も古い呼び出し(プログラムの開始点)が一番上に表示されます。

例と解説

以下の例を考えます。

def divide(x, y):
    return x / y

def main():
    result = divide(5, 0)
    print(result)

main()

上のコードを実行すると、ZeroDivisionErrorが発生し、以下のようなスタックトレースが表示されます。

Traceback (most recent call last):
  File "example.py", line 8, in 
    main()
  File "example.py", line 6, in main
    result = divide(5, 0)
  File "example.py", line 2, in divide
    return x / y
ZeroDivisionError: division by zero

このスタックトレースを解析すると

  1. 最も下の部分は、エラーが発生した具体的な箇所を示しています。この場合、divide関数のreturn x / y行でエラーが起きています。
  2. その上の部分は、divide関数を呼び出した箇所、すなわちmain関数のresult = divide(5, 0)行を示しています。
  3. さらにその上は、main関数を呼び出した箇所、すなわちmain()行を示しています。

このように、スタックトレースはエラーの発生源からプログラムの開始までの呼び出しの順序を示し、エラーの原因を特定する手がかりとなります。


推奨されるエラー処理のパターン

Pythonのエラー処理においては、いくつかの推奨されるパターンやベストプラクティスが存在します。以下に主なものを挙げ、詳細に説明します。

具体的な例外をキャッチする

Pythonのexcept句で例外をキャッチする際、可能な限り具体的な例外を指定することが推奨されます。

# 良い例
try:
    x = 1 / 0
except ZeroDivisionError:
    print("0での割り算はできません。")

# 悪い例
try:
    x = 1 / 0
except Exception:  # 一般的すぎる例外
    print("何らかのエラーが発生しました。")


例外の再発生

捕捉した例外を処理した後、必要に応じて同じ例外を再発生させることができます。

try:
    x = 1 / 0
except ZeroDivisionError:
    print("0での割り算はできません。")
    raise  # 例外を再発生させる


else句を使用する

tryブロックが例外を発生させなかった場合にのみ実行されるelse句を使用することで、コードの意図を明確にすることができます。

try:
    x = 1 / 2
except ZeroDivisionError:
    print("0での割り算はできません。")
else:
    print("割り算は正常に完了しました。")


finally句を活用する

例外が発生してもしなくても実行されるfinally句を使って、リソースの解放などのクリーンアップ作業を行います。

try:
    f = open('file.txt', 'r')
    content = f.read()
except FileNotFoundError:
    print("ファイルが見つかりません。")
finally:
    f.close()  # 必ずファイルを閉じる


エラーメッセージを詳細にする

例外をキャッチした際に、可能な限り詳細な情報をエラーメッセージに含めることで、デバッグが容易になります。

try:
    with open("nonexistent.txt", "r") as file:
        data = file.read()
except FileNotFoundError as e:
    print(f"ファイルが見つかりませんでした: {e}")


カスタム例外を作成する

組み込みの例外クラスだけでは十分でない場合、独自の例外クラスを定義することも考慮すると良いです。

class CustomError(Exception):
    """カスタム例外の説明"""
    pass

try:
    raise CustomError("これはカスタム例外です")
except CustomError as e:
    print(e)


ワーニングの扱い

Pythonでは、プログラムの問題点を指摘するために警告(ワーニング)が提供されています。警告はエラーではないため、プログラムの実行は中断されませんが、将来的な問題を避けるための注意喚起として役立ちます。

warningsモジュール

Pythonのwarningsモジュールを使用すると、警告を発生させることができます。また、発生する警告の挙動を制御することも可能です。

import warnings

# 警告の発生
def api_v1():
    warnings.warn("api_v1は非推奨です。api_v2を使用してください。", DeprecationWarning)

api_v1()

デフォルトでは、一部の警告は表示されませんが、warnings.simplefilter()を使用して警告の表示を制御することができます。

warnings.simplefilter("always")  # すべての警告を常に表示する


警告のカテゴリ

Pythonにはいくつかの組み込み警告カテゴリが存在します。

  • Warning: すべての警告の基底クラス
  • UserWarning: ユーザコードによって生成される警告の基底クラス
  • DeprecationWarning: 廃止予定の機能を使っているときに発生する警告
  • SyntaxWarning: 意図しない操作の疑いがある場合の警告
  • RuntimeWarning: 予想外のランタイム挙動の警告
  • ...など


コマンドラインからのワーニング制御

Pythonスクリプトを実行する際に、コマンドラインオプションを使用して警告の挙動を制御することができます。

  • Wdefault: 警告をデフォルトの動作で表示
  • Wignore: 警告を表示しない
  • Walways: すべての警告を表示
  • ...など
$ python -Walways myscript.py


環境変数を使用した警告の制御

PYTHONWARNINGS環境変数を設定することで、実行するPythonスクリプトの警告の挙動を変更できます。

  • Wdefault: 警告をデフォルトの動作で表示
  • Wignore: 警告を表示しない
  • Walways: すべての警告を表示
  • ...など
$ export PYTHONWARNINGS=always
$ python myscript.py


練習問題1.

以下のコードは実行するとエラーが発生します。try-exceptを用いてこのエラーを捕捉し、"0で除算することはできません。"というエラーメッセージを出力してください。

def divide(x, y):
    return x / y

result = divide(5, 0)
print(result)


練習問題2.

以下のコードは複数のエラーを含んでいます。try-exceptブロックを使用して、発生した例外の種類に応じて異なるエラーメッセージを出力するように修正してください。

data = {"a": 1, "b": 2, "c": 3}

def fetch_data(key):
    return data[key]

def divide(x, y):
    return x / y

# エラー1: 辞書に存在しないキーを参照
value = fetch_data("d")

# エラー2: 0での除算
result = divide(value, 0)
print(result)


練習問題3.

独自の例外ValueTooHighErrorValueTooLowErrorを定義してください。次に、以下のtest_value()関数を修正して、関数内で数値が100より大きい場合はValueTooHighErrorを、10より小さい場合はValueTooLowErrorを発生させるようにしてください。

def test_value(x):
    # ここにコードを書いてください
    return x

try:
    test_value(150)
except Exception as e:
    print(e)
    
try:
    test_value(5)
except Exception as e:
    print(e)