Pythonの例外処理を理解する|エラー・traceback・try・exceptの基本

「Pythonの例外処理を理解する|エラー・traceback・try・exceptの基本」の内容を表す技術イラスト

Pythonでプログラムを書いていると、構文の間違い、未定義の変数、不正な入力などによって処理が止まることがあります。

大切なのは、エラーを消すことだけではありません。どこで、何が原因で失敗したかを読み取り、必要な場合だけ適切に処理できるようにすることです。

この記事では、代表的なエラー、tracebackの読み方、try・except・else・finally・raiseの使い分けを学びます。

目次

今日の到達点

  • SyntaxErrorと実行時の例外を区別できる
  • tracebackから原因となった行と例外の種類を確認できる
  • tryとexceptで予想できる失敗だけを処理できる
  • elseとfinallyの役割を説明できる
  • raiseで不正な値を明示的に拒否できる
  • 例外を握りつぶさず、原因を追跡できるコードを書ける

エラーは原因を知らせる情報

Pythonが処理を続けられないとき、例外が発生します。表示されるメッセージには、失敗した場所と原因を調べるための情報が含まれています。

pressure_text = "high"
pressure_mpa = float(pressure_text)

実行結果の例です。

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    pressure_mpa = float(pressure_text)
ValueError: could not convert string to float: 'high'

最後の行にあるValueErrorが例外の種類です。その後ろには、変換できなかった値が示されています。

SyntaxErrorは実行前の文法エラー

SyntaxErrorは、Pythonの文法としてコードを解釈できないときに発生します。

if pressure_mpa > 16.0
    print("Limit exceeded")

if文の末尾にコロン:がないため、処理を開始できません。

修正例です。

if pressure_mpa > 16.0:
    print("Limit exceeded")

tryで通常の実行時例外を扱う前に、まず構文を修正する必要があります。

よく見る例外

NameError

定義されていない名前を使うとNameErrorが発生します。

print(pressure_mpa)

変数名の綴り、代入の実行順序、変数が有効な範囲を確認します。

TypeError

その型では対応できない操作を行うとTypeErrorが発生します。

pressure_text = "14"
result = pressure_text + 2

文字列と整数は直接加算できません。数値として使う場合は、検証してから変換します。

pressure_text = "14"
pressure_mpa = float(pressure_text)
result = pressure_mpa + 2

print(result)

実行結果です。

16.0

ValueError

型として受け取れる操作でも、値の内容が不適切な場合はValueErrorが発生します。

pressure_mpa = float("high")

float()は文字列を受け取れますが、"high"を数値として解釈できません。

ZeroDivisionError

数値を0で割るとZeroDivisionErrorが発生します。

flow_l_min = 60.0
duration_min = 0.0
average_flow = flow_l_min / duration_min

割り算の直前に、分母が有効な値か確認する必要があります。

KeyErrorとIndexError

辞書に存在しないキーを参照するとKeyError、リストの範囲外を参照するとIndexErrorが発生します。

component = {"name": "Cylinder A"}
print(component["pressure_mpa"])
pressures_mpa = [7.0, 14.0]
print(pressures_mpa[5])

tracebackは最後の行から確認する

tracebackは、例外が発生するまでに通った呼び出し経路を示します。

def calculate_force_n(pressure_mpa, area_mm2):
    return pressure_mpa * area_mm2


def build_report():
    pressure_mpa = float("high")
    return calculate_force_n(pressure_mpa, 1000.0)


print(build_report())

読む順番は次のとおりです。

  1. 最後の行で例外の種類とメッセージを確認する
  2. その直前で実際に失敗したコード行を確認する
  3. 上へたどり、どの関数から呼び出されたかを確認する
  4. 入力値と直前の処理を再現する

長いtracebackでも、最初からすべてを暗記する必要はありません。まず末尾の例外名と、最も内側で失敗した行を探します。

tryとexceptで予想できる失敗を扱う

入力文字列を数値へ変換する処理は、失敗する可能性があります。そのような予想できる失敗をtryで囲みます。

pressure_text = "14.5"

try:
    pressure_mpa = float(pressure_text)
except ValueError:
    print("圧力を数値として解釈できません")
else:
    print(f"Pressure: {pressure_mpa:.2f} MPa")

実行結果です。

Pressure: 14.50 MPa

"high"を指定した場合はexcept ValueErrorが実行されます。

圧力を数値として解釈できません

exceptでは、捕捉する例外を具体的に指定します。

try:
    pressure_mpa = float(pressure_text)
except ValueError as error:
    print(f"入力エラー: {error}")

as errorで例外オブジェクトを受け取ると、ログや診断メッセージへ原因を残せます。

tryの範囲を広げすぎない

次のように大きな処理全体を囲むと、どの操作を保護したかったのか分かりにくくなります。

try:
    pressure_mpa = float(pressure_text)
    area_mm2 = 1000.0
    force_n = pressure_mpa * area_mm2
    print(f"Force: {force_n:.1f} N")
except ValueError:
    print("入力エラー")

変換で発生するValueErrorだけを扱うなら、tryの範囲を変換処理へ限定します。

try:
    pressure_mpa = float(pressure_text)
except ValueError:
    print("圧力を数値として解釈できません")
else:
    area_mm2 = 1000.0
    force_n = pressure_mpa * area_mm2
    print(f"Force: {force_n:.1f} N")

これにより、計算部分で別の不具合が発生したときに、入力変換エラーとして誤認しにくくなります。

elseは例外がなかったときに実行する

elseは、try内で例外が発生しなかったときだけ実行されます。

try:
    pressure_mpa = float("14.0")
except ValueError:
    print("Invalid pressure")
else:
    print(f"Converted: {pressure_mpa:.1f} MPa")

実行結果です。

Converted: 14.0 MPa

成功後の処理をelseへ分けると、tryを必要最小限にできます。

finallyは成功・失敗にかかわらず実行する

finallyは、例外の有無にかかわらず最後に実行されます。

try:
    pressure_mpa = float("14.0")
except ValueError:
    print("Invalid pressure")
finally:
    print("Input processing finished")

実行結果です。

Input processing finished

finallyは、取得した資源を必ず解放したい場合などに使います。ただし、ファイル操作ではwith文を使う方が簡潔な場合があります。

raiseで不正な値を拒否する

数値への変換に成功しても、工学的に妥当とは限りません。たとえば負の受圧面積は計算へ渡すべきではありません。

def calculate_force_n(pressure_mpa, area_mm2):
    if pressure_mpa <= 0:
        raise ValueError("pressure_mpa must be greater than 0")

    if area_mm2 <= 0:
        raise ValueError("area_mm2 must be greater than 0")

    return pressure_mpa * area_mm2

正常値の例です。

force_n = calculate_force_n(14.0, 1000.0)
print(force_n)

実行結果です。

14000.0

異常値を渡すとValueErrorが発生します。

calculate_force_n(14.0, -1000.0)

例外メッセージです。

ValueError: area_mm2 must be greater than 0

関数の入り口で前提条件を確認すると、無意味な計算結果が後工程へ流れることを防げます。

例外を握りつぶさない

次のコードは、すべての例外を捕捉して何もしません。

try:
    pressure_mpa = float(pressure_text)
except Exception:
    pass

これでは、入力ミスだけでなくプログラム上の不具合まで見えなくなる可能性があります。

少なくとも、予想する例外だけを捕捉し、対象値や原因を残します。

try:
    pressure_mpa = float(pressure_text)
except ValueError as error:
    print(f"Invalid pressure {pressure_text!r}: {error}")

回復できない場合は、無理に処理を継続せず、呼び出し元へ例外を伝える設計も必要です。

実務例:複数の入力を検証して計算する

文字列で受け取った圧力と面積を数値へ変換し、有限の正数であることを確認して理論推力を計算します。

import math


def parse_positive_float(text, field_name):
    try:
        value = float(text)
    except ValueError as error:
        raise ValueError(
            f"{field_name} must be a number: {text!r}"
        ) from error

    if not math.isfinite(value):
        raise ValueError(f"{field_name} must be finite")

    if value <= 0:
        raise ValueError(f"{field_name} must be greater than 0")

    return value


def calculate_force_n(pressure_mpa, area_mm2):
    return pressure_mpa * area_mm2


pressure_text = "14.0"
area_text = "1000.0"

try:
    pressure_mpa = parse_positive_float(
        pressure_text,
        "pressure_mpa",
    )
    area_mm2 = parse_positive_float(
        area_text,
        "area_mm2",
    )
except ValueError as error:
    print(f"Input error: {error}")
else:
    force_n = calculate_force_n(pressure_mpa, area_mm2)
    print(f"Force: {force_n:.1f} N")
finally:
    print("Calculation attempt finished")

実行結果です。

Force: 14000.0 N
Calculation attempt finished

pressure_text = "high"へ変更した場合の出力です。

Input error: pressure_mpa must be a number: 'high'
Calculation attempt finished

raise ... from errorは、元の例外との因果関係を保ったまま、用途に合う説明を付けて例外を発生させます。これにより、どの項目の変換に失敗したかと、元の原因の両方を追跡できます。

このコードは次の順序で処理しています。

  1. 文字列をfloatへ変換する
  2. NaNや無限大ではないことを確認する
  3. 0より大きいことを確認する
  4. 検証済みの値だけを計算へ渡す
  5. 失敗理由を表示する
  6. 成功・失敗にかかわらず終了処理を実行する

よくある失敗

exceptだけを書けば安全だと思う

例外を捕捉するとプログラムは停止しにくくなりますが、誤った結果で継続する危険もあります。回復方法が明確な例外だけを処理します。

すべてをExceptionで捕捉する

広い例外捕捉は、タイポや実装上の不具合まで隠す場合があります。まずValueError、KeyErrorなど予想する例外を具体的に指定します。

エラーメッセージから入力値を消す

Invalid inputだけでは原因を再現しにくくなります。秘密情報を含めない範囲で、項目名、行番号、問題の値、例外の種類を残します。

例外発生後も古い値を使う

変換に失敗した変数を、以前の値のまま計算へ使うと危険です。失敗した処理は中断するか、その行だけ明示的に除外します。

練習問題

数値変換の失敗を処理する

文字列"12.5"をfloatへ変換し、変換できない場合はInvalid valueと表示してください。

解答例です。

value_text = "12.5"

try:
    value = float(value_text)
except ValueError:
    print("Invalid value")
else:
    print(value)

実行結果です。

12.5

ゼロ以下を拒否する

流量が0以下の場合にValueErrorを発生させる関数を作ってください。

解答例です。

def validate_flow_l_min(flow_l_min):
    if flow_l_min <= 0:
        raise ValueError("flow_l_min must be greater than 0")

    return flow_l_min

確認する観点は、関数の入り口で不正値を拒否し、単位を変数名へ含めていることです。

行番号を含むエラーを表示する

複数の入力値を変換し、失敗した行番号と値を表示してください。

pressure_texts = ["14.0", "high", "7.0"]

解答例です。

pressure_texts = ["14.0", "high", "7.0"]

for row_no, pressure_text in enumerate(pressure_texts, start=1):
    try:
        pressure_mpa = float(pressure_text)
    except ValueError as error:
        print(
            f"Row {row_no}: invalid value "
            f"{pressure_text!r} ({error})"
        )
        continue

    print(f"Row {row_no}: {pressure_mpa:.1f} MPa")

実行結果です。

Row 1: 14.0 MPa
Row 2: invalid value 'high' (could not convert string to float: 'high')
Row 3: 7.0 MPa

まとめ

  • SyntaxErrorはPythonが文法として解釈できないときに発生する
  • 実行時の問題はValueError、TypeErrorなどの例外として通知される
  • tracebackは最後の行から例外名、メッセージ、失敗行を確認する
  • tryには失敗が予想される最小限の処理を書く
  • exceptでは可能な限り具体的な例外を捕捉する
  • elseは例外がなかったときの処理に使う
  • finallyは成功・失敗にかかわらず実行する
  • raiseで関数の前提条件を明示できる
  • 例外を無言で握りつぶさず、原因を追跡できる情報を残す
  • 型だけでなく、有限性、範囲、単位も検証する

例外処理は、エラー表示を隠す仕組みではありません。失敗を検出し、原因を伝え、安全に回復できる場合だけ処理を継続するための仕組みです。

参考情報

参考になったらシェアしてください
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

機械設計・油圧・CAD・Python・AIなど、ものづくりに関わる技術を扱っています。工学知識を整理・構造化し、設計や自動化に再利用できる形へ変えていくことを目指しています。

目次