Skip to content
トピック
Python
Python Try Except:例外を正しく処理する方法

Python Try Except:例外を正しく処理する方法

Updated on

あなたのPythonスクリプトはファイルを読み取り、データを解析し、APIに送信します。あなたのマシンでは完璧に動作します。しかし、ファイルパスが間違っている、JSONが不正な形式である、またはネットワークがダウンしているサーバーで実行すると、プログラムはトレースバックでクラッシュし、パイプライン全体が停止します。これがtry/exceptが解決する問題です。エラーにプログラムを終了させる代わりに、エラーをキャッチし、処理し、実行を継続します。

📚

Pythonの例外とは何か?

例外とは、プログラムの通常のフローを中断するイベントです。Pythonが実行できない操作に遭遇したとき──ゼロ除算、存在しないディクショナリキーへのアクセス、存在しないファイルの開封──例外オブジェクトを作成し、実行を停止します。その例外を何もキャッチしなければ、プログラムは終了してトレースバックを表示します。

# This crashes the program
result = 10 / 0

出力:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    result = 10 / 0
ZeroDivisionError: division by zero

例外は構文エラーとは異なります。構文エラーは、Pythonがコードを全く解析できないことを意味します。例外は、コードが正常に解析された後、実行中に発生します。

基本的なTry/Except構文

try/exceptブロックは、失敗する可能性のあるコードを試行し、失敗した場合の処理を定義できます。

try:
    result = 10 / 0
except ZeroDivisionError:
    print("Cannot divide by zero.")

出力:

Cannot divide by zero.

プログラムはクラッシュしません。Pythonはtry内のコードを実行します。ZeroDivisionErrorが発生すると、実行はexceptブロックにジャンプします。try/exceptの後のすべてが通常通り続行されます。

例外オブジェクトをキャプチャしてエラーメッセージを確認することもできます:

try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Error: {e}")

出力:

Error: division by zero

特定の例外をキャッチする

Pythonには数十の組み込み例外タイプがあります。正しいものをキャッチすることで、コードが正確になります。以下は最も一般的に処理する例外です。

ValueError

関数が正しい型だが不適切な値の引数を受け取ったときに発生します。

try:
    number = int("not_a_number")
except ValueError as e:
    print(f"Invalid value: {e}")

出力:

Invalid value: invalid literal for int() with base 10: 'not_a_number'

TypeError

間違った型のオブジェクトに操作が適用されたときに発生します。

try:
    result = "hello" + 42
except TypeError as e:
    print(f"Type error: {e}")

出力:

Type error: can only concatenate str (not "int") to str

FileNotFoundError

存在しないファイルを開こうとしたときに発生します。

try:
    with open("nonexistent_file.txt", "r") as f:
        content = f.read()
except FileNotFoundError:
    print("File not found. Check the file path.")

KeyError

存在しないディクショナリキーにアクセスしたときに発生します。

data = {"name": "Alice", "age": 30}
 
try:
    email = data["email"]
except KeyError as e:
    print(f"Missing key: {e}")

出力:

Missing key: 'email'

IndexError

範囲外のリストインデックスにアクセスしたときに発生します。

items = [10, 20, 30]
 
try:
    value = items[5]
except IndexError:
    print("Index out of range.")

複数のExceptブロック

異なる例外タイプを個別のexceptブロックで処理できます。Pythonは順番にチェックし、最初にマッチしたものを実行します。

def parse_config(raw_value):
    try:
        parts = raw_value.split(":")
        key = parts[0]
        value = int(parts[1])
        return {key: value}
    except IndexError:
        print("Config format error: missing colon separator.")
    except ValueError:
        print("Config format error: value is not a number.")
    except AttributeError:
        print("Config format error: input is not a string.")
 
parse_config("timeout:30")    # Returns {'timeout': 30}
parse_config("timeout")       # Config format error: missing colon separator.
parse_config("timeout:abc")   # Config format error: value is not a number.
parse_config(12345)            # Config format error: input is not a string.

タプルを使用して、1つのexceptブロックで複数の例外をキャッチすることもできます:

try:
    value = int(input("Enter a number: "))
    result = 100 / value
except (ValueError, ZeroDivisionError) as e:
    print(f"Invalid input: {e}")

Elseブロック

elseブロックはtryブロックで例外が発生しなかった場合にのみ実行されます。「正常パス」のコードをエラー処理コードから分離します。

try:
    number = int("42")
except ValueError:
    print("That is not a valid number.")
else:
    print(f"Successfully parsed: {number}")

出力:

Successfully parsed: 42

elseブロックが便利なのは、その中で発生した例外が先行するexceptブロックではキャッチされないためです。これにより、成功パスコード内のバグを誤って隠すことを防ぎます。

filename = "data.txt"
 
try:
    f = open(filename, "r")
except FileNotFoundError:
    print(f"File '{filename}' does not exist.")
else:
    content = f.read()
    f.close()
    print(f"Read {len(content)} characters.")

open()が失敗した場合、exceptブロックが処理します。open()が成功した場合、elseブロックがファイルを読み取ります。f.read()中のエラーはここではキャッチされません──上位に伝播しますが、これが正しい動作です。

Finallyブロック

finallyブロックは例外が発生したかどうかにかかわらず常に実行されます。クリーンアップコードの適切な場所です:ファイルのクローズ、ロックの解放、データベースからの切断。

f = None
try:
    f = open("data.txt", "r")
    content = f.read()
except FileNotFoundError:
    print("File not found.")
finally:
    if f is not None:
        f.close()
        print("File handle closed.")

finallyブロックはtryブロックが値を返した場合や、処理されない例外が発生した場合でも実行されます。

def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None
    finally:
        print("Division attempted.")
 
result = divide(10, 3)
# Output: Division attempted.
# result = 3.3333333333333335
 
result = divide(10, 0)
# Output: Division attempted.
# result = None

完全なTry/Except/Else/Finallyパターン

4つのブロックすべてがどのように連携するかを示します:

import json
 
def load_config(filepath):
    """Load and parse a JSON config file."""
    f = None
    try:
        f = open(filepath, "r")
        data = json.load(f)
    except FileNotFoundError:
        print(f"Config file '{filepath}' not found.")
        return {}
    except json.JSONDecodeError as e:
        print(f"Invalid JSON in '{filepath}': {e}")
        return {}
    else:
        print(f"Config loaded successfully with {len(data)} keys.")
        return data
    finally:
        if f is not None:
            f.close()
            print("File handle released.")
 
config = load_config("settings.json")
ブロック実行タイミング目的
try常に(最初)例外を発生させる可能性のあるコードを含む
except一致する例外が発生した場合のみエラーを処理する
elsetryで例外が発生しなかった場合のみ成功パスのロジックを実行する
finally常に(最後)必ず実行されるクリーンアップコード

一般的な組み込み例外リファレンス

Pythonには組み込み例外の階層構造が含まれています。以下は最も頻繁に遭遇するものです。

例外発生条件
Exceptionほとんどの例外の基底クラスすべての非システム終了例外の親
ValueError正しい型だが不適切な値int("abc")
TypeError操作に対して不適切な型"text" + 5
KeyErrorディクショナリキーが存在しないd["missing"]
IndexErrorリストインデックスが範囲外[1,2,3][10]
FileNotFoundErrorファイルが存在しないopen("no.txt")
ZeroDivisionErrorゼロによる除算またはモジュロ1 / 0
AttributeErrorオブジェクトに属性がないNone.append(1)
ImportErrorモジュールのインポートが失敗import nonexistent
OSErrorOS操作が失敗ディスクフル、アクセス拒否
StopIterationイテレータに要素がない使い果たされたイテレータでのnext()
RuntimeError一般的なランタイムエラーその他のエラーのキャッチオール

これらはすべてBaseExceptionを継承しています。実際には、Exceptionまたはそのサブクラスをキャッチすべきであり、BaseExceptionを直接キャッチすべきではありません(SystemExitKeyboardInterruptを含むため)。

raiseで例外を発生させる

コードが無効な条件を検出したとき、明示的に例外を発生させることができます。これにより前提条件を強制し、呼び出し元にエラーを通知できます。

def set_age(age):
    if not isinstance(age, int):
        raise TypeError(f"Age must be an integer, got {type(age).__name__}")
    if age < 0 or age > 150:
        raise ValueError(f"Age must be between 0 and 150, got {age}")
    return age
 
# Valid usage
print(set_age(25))  # 25
 
# Invalid usage
try:
    set_age(-5)
except ValueError as e:
    print(e)  # Age must be between 0 and 150, got -5
 
try:
    set_age("thirty")
except TypeError as e:
    print(e)  # Age must be an integer, got str

exceptブロック内で引数なしのraiseを使用して、現在の例外を再発生させることもできます。これはエラーをログに記録してから伝播させたい場合に便利です。

import logging
 
try:
    result = 10 / 0
except ZeroDivisionError:
    logging.error("Division by zero encountered")
    raise  # re-raises the original ZeroDivisionError

カスタム例外クラス

大規模なプロジェクトでは、独自の例外クラスを定義します。カスタム例外はエラー処理をより読みやすくし、呼び出し元が特定の障害モードをキャッチできるようにします。

class ValidationError(Exception):
    """Raised when input data fails validation."""
    def __init__(self, field, message):
        self.field = field
        self.message = message
        super().__init__(f"Validation failed on '{field}': {message}")
 
class DatabaseConnectionError(Exception):
    """Raised when the database connection fails."""
    pass
 
# Usage
def validate_email(email):
    if "@" not in email:
        raise ValidationError("email", "Must contain @ symbol")
    if "." not in email.split("@")[1]:
        raise ValidationError("email", "Domain must contain a dot")
    return email
 
try:
    validate_email("userexample.com")
except ValidationError as e:
    print(e)          # Validation failed on 'email': Must contain @ symbol
    print(e.field)    # email
    print(e.message)  # Must contain @ symbol

カスタム例外はBaseExceptionではなくExceptionを継承すべきです。関連する例外を共通の基底クラスの下にグループ化して、呼び出し元が広いまたは狭いカテゴリをキャッチできるようにします:

class AppError(Exception):
    """Base exception for this application."""
    pass
 
class ConfigError(AppError):
    pass
 
class NetworkError(AppError):
    pass
 
# Caller can catch all app errors or specific ones
try:
    raise NetworkError("Connection timed out")
except AppError as e:
    print(f"Application error: {e}")

Python例外処理のベストプラクティス

1. ベアな例外をキャッチしない

ベアなexcept:KeyboardInterruptSystemExitを含むすべてをキャッチします。これはバグを隠し、デバッグを悪夢にします。

# BAD - catches everything, hides real bugs
try:
    do_something()
except:
    pass
 
# GOOD - catches specific exceptions
try:
    do_something()
except ValueError as e:
    logging.warning(f"Invalid value: {e}")

広い範囲をキャッチする必要がある場合は、代わりにexcept Exceptionを使用してください。これでもKeyboardInterruptSystemExitは伝播します。

2. Tryブロックを小さく保つ

例外を発生させる可能性のあるコードのみをtryブロック内に置きます。大きなtryブロックでは、どの行がエラーを引き起こしたか不明確になります。

# BAD - too much code in try
try:
    data = load_data()
    cleaned = clean_data(data)
    result = analyze(cleaned)
    save_results(result)
except Exception as e:
    print(f"Something failed: {e}")
 
# GOOD - narrow try blocks
data = load_data()
cleaned = clean_data(data)
 
try:
    result = analyze(cleaned)
except ValueError as e:
    print(f"Analysis failed: {e}")
    result = default_result()
 
save_results(result)

3. 例外をログに記録し、隠さない

passで例外を飲み込むと、目に見えないバグが生まれます。常にエラーをログに記録するか報告してください。

import logging
 
try:
    process_record(record)
except ValueError as e:
    logging.error(f"Failed to process record {record['id']}: {e}")

4. 具体的な例外タイプを使用する

可能な限り具体的な例外タイプをキャッチしてください。これにより、予期しないエラーを誤って処理することを防ぎます。

アプローチキャッチ対象リスクレベル
except:SystemExitを含むすべて非常に高い
except Exception:すべての標準例外高い
except ValueError:ValueErrorのみ低い
except (ValueError, TypeError):2つの特定タイプ低い

5. Finallyでリソースをクリーンアップするかコンテキストマネージャを使用する

ファイルハンドル、データベース接続、ロックには、常にfinallyまたは(さらに良いのは)with文を使用してください。

# Prefer context managers for resource cleanup
with open("data.txt", "r") as f:
    content = f.read()
# File is automatically closed, even if an exception occurs

実世界の例

JSONファイルの読み取りと解析

import json
 
def read_json_config(filepath):
    """Read a JSON configuration file with proper error handling."""
    try:
        with open(filepath, "r") as f:
            config = json.load(f)
    except FileNotFoundError:
        print(f"Config file not found: {filepath}")
        return None
    except PermissionError:
        print(f"No permission to read: {filepath}")
        return None
    except json.JSONDecodeError as e:
        print(f"Invalid JSON at line {e.lineno}, column {e.colno}: {e.msg}")
        return None
    else:
        print(f"Loaded config with keys: {list(config.keys())}")
        return config
 
config = read_json_config("app_config.json")
if config:
    db_host = config.get("database_host", "localhost")

HTTP APIコールの実行

import urllib.request
import urllib.error
import json
 
def fetch_user(user_id):
    """Fetch user data from an API with retry logic."""
    url = f"https://jsonplaceholder.typicode.com/users/{user_id}"
    max_retries = 3
 
    for attempt in range(1, max_retries + 1):
        try:
            with urllib.request.urlopen(url, timeout=5) as response:
                data = json.loads(response.read().decode())
            return data
        except urllib.error.HTTPError as e:
            if e.code == 404:
                print(f"User {user_id} not found.")
                return None
            print(f"HTTP error {e.code} on attempt {attempt}")
        except urllib.error.URLError as e:
            print(f"Network error on attempt {attempt}: {e.reason}")
        except json.JSONDecodeError:
            print("API returned invalid JSON.")
            return None
 
    print(f"All {max_retries} attempts failed.")
    return None
 
user = fetch_user(1)
if user:
    print(f"Found user: {user['name']}")

CSVデータの処理

import csv
 
def process_sales_data(filepath):
    """Process a CSV file with robust error handling."""
    results = []
 
    try:
        with open(filepath, "r", newline="") as f:
            reader = csv.DictReader(f)
            for row_num, row in enumerate(reader, start=2):
                try:
                    amount = float(row["amount"])
                    quantity = int(row["quantity"])
                    results.append({
                        "product": row["product"],
                        "total": amount * quantity,
                    })
                except KeyError as e:
                    print(f"Row {row_num}: Missing column {e}")
                except ValueError as e:
                    print(f"Row {row_num}: Invalid number - {e}")
    except FileNotFoundError:
        print(f"File not found: {filepath}")
    except PermissionError:
        print(f"Cannot read file: {filepath}")
 
    return results

Try/Except vs If/Else:どちらを使うべきか

Pythonは EAFP原則に従います:"Easier to Ask Forgiveness than Permission"(許可を求めるより許しを請う方が簡単)。これはLBYLアプローチ:"Look Before You Leap"(飛ぶ前に見よ)と対照的です。

# LBYL (Look Before You Leap) - using if/else
if "email" in user_data:
    email = user_data["email"]
else:
    email = "unknown"
 
# EAFP (Easier to Ask Forgiveness) - using try/except
try:
    email = user_data["email"]
except KeyError:
    email = "unknown"
基準if/else (LBYL)try/except (EAFP)
最適な場面チェックが安価でエラーが頻繁エラーがまれまたはチェックが高コスト
パフォーマンス(エラーなし)やや遅い(毎回追加チェック)やや速い(チェックのオーバーヘッドなし)
パフォーマンス(エラー発生時)同じ遅い(例外作成にオーバーヘッド)
競合状態あり得る(チェックと使用の間に状態が変わる可能性)なし(アトミック操作)
可読性単純な条件には明確複数の方法で失敗する可能性がある操作に最適
ファイル操作if os.path.exists(path) -- チェックとオープンの間にファイルが削除される可能性try: open(path) -- 実際の失敗を処理
ディクショナリアクセスif key in dict -- シンプルで高速try: dict[key] -- またはdict.get(key, default)を使用

try/exceptを使用する場面:

  • 失敗ケースがまれ(例外は「エラーなし」パスに最適化されている)。
  • チェック自体が操作と同じくらい高コスト(例:ファイルが存在するか確認してから開く)。
  • 一連の操作で複数の問題が発生する可能性がある。
  • 競合状態が問題になる(特にファイルとネットワークリソース)。

if/elseを使用する場面:

  • 条件のチェックが安価で、失敗が頻繁。
  • 処理前にユーザー入力を検証する。
  • ロジックが条件文としてより明確に読める。

RunCellで例外をより速くデバッグする

Jupyterノートブックで作業していると、例外が分析フローを中断することがあります。DataFrameの5万行目でKeyErrorに遭遇したり、パイプラインの3セル先でTypeErrorが表面化したりします。根本原因を追跡するには、トレースバックをスクロールし、手動で変数を検査する必要があります。

RunCell (opens in a new tab)は、Jupyter内で直接動作するAIエージェントです。完全なトレースバックを読み取り、現在のスコープの変数を検査し、コンテキスト内で修正を提案します。例外処理でどのように役立つかを説明します:

  • トレースバック分析。 RunCellは例外チェーンを解析し、ネストされた関数呼び出しでも、どの変数や操作が失敗を引き起こしたかを特定します。
  • 修正提案。 Stack Overflowを検索する代わりに、RunCellはすぐに実行できる修正されたコードセルを生成します。try/exceptを追加すべきか、型変換を修正すべきか、欠落キーを処理すべきかを判断します。
  • 予防チェック。 RunCellはコードをスキャンし、例外を発生させる可能性のある操作──.get()なしでのディクショナリキーアクセスや、ゼロチェックなしでの除算──をセル実行前に警告します。

RunCellは既存のJupyter環境内で動作するため、実際のデータと変数にアクセスできます。提供される提案は一般的なアドバイスではなく、あなたの状況に特化したものです。

FAQ

Pythonのtry/exceptとtry/catchの違いは何ですか?

Pythonはtry/exceptを使用し、try/catchは使用しません。try/catch構文はJava、JavaScript、C++などの言語に属します。Pythonではキーワードはexceptです。機能は同じです:失敗する可能性のあるコードを試行し、失敗した場合のハンドラを定義します。

Pythonで複数のexceptブロックを使用できますか?

はい。必要な数だけexceptブロックを連鎖でき、それぞれ異なる例外タイプをキャッチします。Pythonは順番に評価し、最初にマッチしたブロックを実行します。タプルを使用して1つのブロックで複数の例外をキャッチすることもできます:except (ValueError, TypeError) as e:

try/exceptでelseをいつ使うべきですか?

tryブロックが成功した場合にのみ実行すべきコードがある場合にelseブロックを使用します。主な利点は、elseブロック内で発生した例外が先行するexceptブロックでキャッチされないため、無関係なエラーを誤って隠すことを防ぐことです。

Pythonでfinallyは常に実行されますか?

はい。finallyブロックは、tryブロックが正常に完了した場合、処理された例外を発生させた場合、または処理されない例外を発生させた場合のいずれでも実行されます。tryまたはexceptブロックにreturn文が含まれていても実行されます。唯一の例外は、Pythonプロセスが外部から強制終了された場合、またはos._exit()が呼び出された場合です。

Pythonでカスタム例外を作成するにはどうすればよいですか?

Exceptionを継承する新しいクラスを作成します。カスタム属性を追加し、__init__メソッドをオーバーライドできます。例:class MyError(Exception): pass。より複雑な場合には、エラーコードやコンテキストデータなどのフィールドを追加します。常にBaseExceptionではなくExceptionを継承してください。

まとめ

Pythonのtry/exceptはランタイムエラーを処理する標準的なメカニズムです。特定の例外をキャッチし、クリーンアップコードを実行し、問題が発生してもプログラムを安定させることができます。完全なパターン──try/except/else/finally──はすべてのシナリオをカバーします:操作の試行、失敗の処理、成功ロジックの実行、リソースのクリーンアップ。

主要な原則は明快です。広い例外ではなく、具体的な例外をキャッチしましょう。tryブロックを小さく保ちましょう。エラーを隠すのではなく、常にログに記録するか報告しましょう。クリーンアップにはfinallyまたはコンテキストマネージャを使用しましょう。自分のコードでは明確なエラーメッセージとともに意味のある例外を発生させましょう。

ファイルの読み取り、APIコール、ユーザー入力の処理のいずれであっても、適切な例外処理は午前2時にクラッシュするスクリプトと、エラーをログに記録して実行を継続するスクリプトの違いです。基本から始めましょう──失敗する可能性のあるコードを囲む簡単なtry/except──そしてプロジェクトの成長に伴い、カスタム例外階層を構築していきましょう。

📚