閉じる

【Python】raiseでエラーを発生させる正しい書き方とNG例まとめ

プログラムを書いているとき、「この状況は本来ありえない」「この値は受け取ってはいけない」といった場面で、あえてエラーを起こしたくなることがあります。

Pythonではraise文を使うことで、任意のタイミングで例外を発生させることができます。

本記事では、Pythonのraiseの正しい使い方と、やりがちなNGパターンを、図解とコード例を交えながら丁寧に解説します。

raiseとは何か

raiseの役割と例外(Exception)の関係

Pythonにおける例外(Exception)とは、実行中に発生した異常な状態を表現するオブジェクトです。

何か問題が起きたとき、Pythonは通常Exceptionクラスやそのサブクラス(例えばValueErrorTypeErrorなど)のインスタンスを作り、それを「投げる(raiseする)」ことで、現在の処理を中断して例外処理へ制御を移します。

raise文は、この「例外を投げる」操作を自分で明示的に実行するための構文です。

Python処理系が自動的にエラーを出すだけでなく、開発者が意図的に「ここはエラーとして扱ってほしい」という状況を示すために使います。

エラーを「発生させる」ことが必要な場面

実務のコードでは、Pythonが自動的に発生させる例外だけに頼るのではなく、自分でエラーを検出して積極的に例外を発生させることが非常に重要です。

例えば次のようなケースがあります。

1つ目は、ユーザー入力や関数の引数が仕様と合わない場合です。

例えば、「年齢は0以上でなければならない」「ファイルパスは必須である」といったビジネスルールに違反しているときは、ただ処理を続けるのではなく、明示的にValueErrorなどをraiseした方が安全です。

2つ目は、外部サービスやファイルアクセスに失敗したときです。

ここでも、単にprintでエラーを表示するだけで終わらせるのではなく、上位のレイヤーが対処できるように例外として伝搬させることで、再試行やロールバックなどの処理を統一的に行えます。

3つ目は、「本来起こるはずがない状態」まで到達してしまった場合です。

分岐の漏れやロジックの破綻など、バグの兆候を検知したらraise RuntimeErrorなどで強制的に問題を表面化させることで、早期発見・早期修正につながります。

Pythonのraiseの基本的な書き方

単純な例外発生の書き方と使い方

基本形はとてもシンプルで、例外クラス、もしくはそのインスタンスをraiseの後ろに書くだけです。

Python
# シンプルに例外を発生させる例

def do_something():
    # ここでは、何らかの条件で処理を続行できないと判断したとします
    raise RuntimeError("処理を続行できません")

do_something()  # ここでRuntimeErrorが発生する

このコードを実行すると、RuntimeErrorがスローされ、捕まえるtry-exceptがなければプログラムはスタックトレースを出力して終了します。

Traceback (most recent call last):
  File "example.py", line 7, in <module>
    do_something()
  File "example.py", line 5, in do_something
    raise RuntimeError("処理を続行できません")
RuntimeError: 程処理を続行できません

日常的にはValueErrorTypeErrorRuntimeErrorあたりを使うことが多く、意味に合った例外クラスを選ぶことが大切です。

raise Exceptionとraise Exception()の違い

raiseには大きく2通りの書き方があります。

Python
raise Exception       # 1. 例外クラスを渡す
raise Exception()     # 2. 例外インスタンスを渡す

Python 3ではどちらも有効で、最終的にはどちらも例外インスタンスがスローされるため、動作としてはほぼ同じに見えます。

Python
# 両者が実行時にどう扱われるかを確認する例

def raise_class():
    raise ValueError

def raise_instance():
    raise ValueError("メッセージ付き")

for func in (raise_class, raise_instance):
    try:
        func()
    except ValueError as e:
        print("関数:", func.__name__, "| 型:", type(e), "| メッセージ:", str(e) or "(なし)")
実行結果
関数: raise_class | 型: <class 'ValueError'> | メッセージ: (なし)
関数: raise_instance | 型: <class 'ValueError'> | メッセージ: メッセージ付き

このように、クラスを渡した場合もPython内部でインスタンス化されます。

ただし、メッセージや追加情報を持たせたいときには必ずインスタンスを明示的に生成する必要があります。

一般的なスタイルとしては、常にインスタンスで書く(例:cst-code>raise ValueError(“…”))ことを推奨します。

その方がメッセージを付けやすく、コードを眺めたときの意図も明確です。

メッセージ付きでraiseする書き方

例外はメッセージとセットで設計することが重要です。

メッセージがない例外は、トラブルシューティングのときに非常に困ります。

Python
def set_age(age: int):
    # 不正な値が渡された場合はValueErrorをメッセージ付きで発生させる
    if age < 0:
        # 何がダメなのかがすぐ分かるように具体的に書くことが大切です
        raise ValueError("ageは0以上でなければなりません。指定値: {}".format(age))

    print("年齢を{}歳に設定しました。".format(age))

try:
    set_age(-5)
except ValueError as e:
    print("エラー:", e)
実行結果
エラー: ageは0以上でなければなりません。指定値: -5

「何が」「なぜ」問題なのかが分かるメッセージを心がけると、バグ調査が格段に楽になります。

特に業務システムでは、入力値そのものをメッセージに含めると原因特定に役立ちます。

正しいraiseの使い方の実例

if文で条件チェックしてraiseするパターン

業務ロジックでは、処理の最初で条件をチェックし、問題があればraiseで例外を投げる「ガード節」のスタイルがよく使われます。

Python
def withdraw(balance: int, amount: int) -> int:
    # ガード節(Guard Clause)で不正な条件を早期チェックします
    if amount <= 0:
        raise ValueError("出金額は正の整数でなければなりません。指定額: {}".format(amount))

    if amount > balance:
        raise ValueError("残高不足です。残高: {}, 出金額: {}".format(balance, amount))

    # 条件をクリアした場合のみ本来の処理を行います
    return balance - amount

try:
    new_balance = withdraw(1000, 2000)
except ValueError as e:
    print("エラー発生:", e)
実行結果
エラー発生: 残高不足です。残高: 1000, 出金額: 2000

このようにif文で条件を明示し、ダメな場合はすぐにraiseすることで、関数の本体をシンプルに保つことができます。

関数内での引数チェックとValueErrorのraise

引数の妥当性チェックには、意味に応じた組み込み例外クラスを使うことが推奨されます。

特に、値の範囲や形式が不正な場合はValueErrorが標準的です。

Python
def set_discount_rate(rate: float) -> None:
    """
    割引率を設定する関数です。
    rateは0.0〜1.0の範囲でなければなりません。
    """
    if not isinstance(rate, (int, float)):
        raise TypeError("rateは数値でなければなりません。受け取った型: {}".format(type(rate)))

    if not (0.0 <= rate <= 1.0):
        raise ValueError("rateは0.0〜1.0の範囲で指定してください。指定値: {}".format(rate))

    print("割引率を{:.0%}に設定しました。".format(rate))

try:
    set_discount_rate(1.5)
except (TypeError, ValueError) as e:
    print("設定エラー:", e)
実行結果
設定エラー: rateは0.0〜1.0の範囲で指定してください。指定値: 1.5

型の問題はTypeError、値の範囲や意味の問題はValueErrorという使い分けをすると、呼び出し側がエラーの種類に応じて処理を分けやすくなります。

try-except内でraiseを使ったエラーハンドリング

例外を一度捕まえてから、別の例外として投げ直すこともよくあります。

これは、低レベルのエラーをドメイン固有のエラーに変換するときなどに使われます。

Python
def load_config(path: str) -> dict:
    import json

    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError as e:
        # ファイルが存在しない場合は、より意味の分かりやすい例外に変換する
        raise RuntimeError("設定ファイルが見つかりません: {}".format(path)) from e
    except json.JSONDecodeError as e:
        # JSONの構文が不正な場合も、分かりやすいメッセージをつけて再度raise
        raise RuntimeError("設定ファイルの形式が不正です: {}".format(path)) from e

try:
    config = load_config("config.json")
except RuntimeError as e:
    print("設定読み込みエラー:", e)
実行結果
設定読み込みエラー: 設定ファイルが見つかりません: config.json

上の例では、raise ... from eを使って元の例外(FileNotFoundErrorなど)との関連を残したまま、新しい例外を発生させています。

このfrom構文については、後半のNG例の章で詳しく触れます。

ログ出力と組み合わせたraiseの書き方

実サービスでは、エラーをただraiseするだけでなく、ログに記録した上で上位へ伝えることがよくあります。

Python
import logging

logger = logging.getLogger(__name__)

def process_order(order_id: str) -> None:
    try:
        # ここで何らかの処理を行う想定
        raise ConnectionError("在庫管理システムに接続できません")
    except ConnectionError as e:
        # ログに詳細な情報を残す
        logger.error("注文処理中に接続エラーが発生しました。order_id=%s, error=%s", order_id, e)
        # 上位に知らせるために例外を再度発生させる
        raise

# 実行例(簡易的にコンソールへログを出す設定)
if __name__ == "__main__":
    logging.basicConfig(level=logging.ERROR, format="%(levelname)s:%(name)s:%(message)s")
    try:
        process_order("ORDER-123")
    except ConnectionError:
        print("ユーザー向けには「システムエラーが発生しました」と表示するなどの処理を行う")
実行結果
ERROR:__main__:注文処理中に接続エラーが発生しました。order_id=ORDER-123, error=在庫管理システムに接続できません
ユーザー向けには「システムエラーが発生しました」と表示するなどの処理を行う

ここで使っているraiseだけの書き方は、現在処理中の例外をそのまま再スローするという意味になります。

ログはここで出し、ユーザーへのメッセージ表示はさらに上位のレイヤーで行うという責務分離も重要なポイントです。

PythonのraiseでやりがちなNG例

素のExceptionを乱用してしまうNGパターン

何でもかんでもExceptionで投げるのは避けた方がよいです。

Python
# NG例: 何でもExceptionで投げてしまう
def parse_price(text: str) -> int:
    try:
        return int(text)
    except Exception:
        # なんとなく全部Exceptionで投げ直してしまう
        raise Exception("価格の解析に失敗しました")

このような書き方をすると、どんな種類のエラーが起きたのか分からず、呼び出し側が適切に対処しにくくなります

本来は例えば次のように書くのが望ましいです。

Python
# 良い例: 具体的な例外クラスを使う
def parse_price(text: str) -> int:
    try:
        return int(text)
    except ValueError as e:
        # もともとValueErrorなので、必要ならメッセージを足して再度ValueErrorとしてraise
        raise ValueError("価格の解析に失敗しました。入力値: {}".format(text)) from e

意味が曖昧なExceptionではなく、できるだけ具体的な標準例外、あるいは自作の例外クラスを使うことで、コードの可読性と保守性が大きく向上します。

エラー情報を隠してしまうraiseの書き方

次のような書き方はNGです。

Python
def divide(a: int, b: int) -> float:
    try:
        return a / b
    except ZeroDivisionError:
        # NG: 元の例外情報を完全に捨ててしまっている
        raise RuntimeError("計算に失敗しました")

これでは、なぜ失敗したのか(0で割ったのか、他の理由か)がスタックトレースから分からなくなってしまいます

正しくは、fromを使って元の例外をチェーンするか、単にメッセージを補足するだけならraiseだけで再スローします。

Python
def divide(a: int, b: int) -> float:
    try:
        return a / b
    except ZeroDivisionError as e:
        # 良い例1: 元例外をチェーンする
        raise RuntimeError("0で割ろうとしました。a={}, b={}".format(a, b)) from e

def divide_simple(a: int, b: int) -> float:
    try:
        return a / b
    except ZeroDivisionError:
        # 良い例2: メッセージだけ追加した上で同じ例外を再スロー
        raise  # ここで元のZeroDivisionErrorがそのまま再スローされる

元の例外のスタックトレースを失わないことが、トラブルシューティングの観点からとても重要です。

exceptで例外を握りつぶしてからraiseしないNG例

次のようにexceptで例外を捕まえたあと、何もせずに黙って処理を続けてしまうのもNGです。

Python
# NG例: 例外を握りつぶしてしまう
def do_task():
    try:
        dangerous_operation()
    except Exception:
        # 何もせずにスルーしてしまう
        pass

def dangerous_operation():
    raise RuntimeError("致命的なエラー")

do_task()
print("ここまで到達してしまう")  # 本来は到達すべきでないのに到達してしまう
実行結果
ここまで到達してしまう

このコードでは、致命的なエラーが起きたのに、それを無視して処理が続行されてしまいます

結果として状態が壊れたり、後続の処理でさらにわかりにくい不具合が発生したりします。

どうしても例外を無視してよい場面は非常に限定的です。

多くの場合は少なくともログを残すか、上位へ伝えるべきです。

Python
# 良い例: ログを出してから再スローする
import logging
logger = logging.getLogger(__name__)

def do_task():
    try:
        dangerous_operation()
    except RuntimeError as e:
        logger.error("タスク中に致命的なエラーが発生しました: %s", e)
        raise  # 必ず再スローすることで、上位にエラーを伝えます

def dangerous_operation():
    raise RuntimeError("致命的なエラー")

「何があっても落ちてはいけない」よりも「問題があれば早めに落ちる」設計の方が、結果として信頼性が高くなります。

Pythonのraise fromを誤用するケース

raise 新しい例外 from 元の例外は便利な構文ですが、原因と結果の関係が曖昧なときに無理に使うと、スタックトレースの解釈が難しくなります

誤用の一例を示します。

Python
# NG例: 因果関係がよく分からないのにfromを付けてしまう
def process_data():
    try:
        do_io()
        do_calculation()
    except IOError as e:
        # 実はIOErrorは無関係なのに、計算エラーに無理やりfromでくっつけている
        raise ValueError("計算に失敗しました") from e  # 本当にIOErrorが原因?

def do_io():
    # ここではIOエラーは発生しないとする
    pass

def do_calculation():
    raise ValueError("0割りエラー")

この例では、実際にはdo_calculationValueErrorが発生しているのに、IOErrorとの因果関係があるかのように見えてしまいます。

「どの例外が本当の原因なのか」が見えにくくなり、デバッグを混乱させます

fromを使うのは、たとえば「ファイルを開こうとしたが失敗 → 業務上の設定読み込みエラーに変換する」といった、明確な「原因→結果」の関係がある場合に限るのがよいです。

Python
# 良い例: 原因と結果が明確な場合だけfromを使う
def load_user_settings(path: str):
    import json
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except (FileNotFoundError, PermissionError, json.JSONDecodeError) as e:
        # 「設定が読み込めなかった」という高レベルなエラーに変換する
        raise RuntimeError("ユーザー設定を読み込めませんでした: {}".format(path)) from e

逆に、単にログを出して同じ例外を投げ直すだけなら、raiseだけを書くのがシンプルで正しいです。

Python
try:
    do_something()
except ValueError as e:
    logger.warning("値エラーが発生しました: %s", e)
    raise  # fromは書かない

まとめ

Pythonのraiseは、「異常を早期に検知し、明確な形で外へ伝える」ための重要な道具です。

本記事では、raise ValueError("理由")のような基本の書き方から、if文によるガード節、引数チェック、ログとの連携、そしてraise fromを使った例外チェーンまでを解説しました。

一方で、素のException乱用や例外の握りつぶし、原因を隠してしまう再スローなどはNGパターンです。

意味のある例外クラスと分かりやすいメッセージを心がけ、元のエラー情報を保ちながら適切にraiseを使うことで、保守しやすく信頼性の高いPythonコードを書くことができます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!