閉じる

Pythonのraiseの使い方: 基本と実例で学ぶ意図的な例外発生

意図的にエラーを発生させるraiseは、入力チェックや未実装機能の通知などでプログラムの意図を明確にできる強力な仕組みです。

正しい場面で正しい例外を発生させることは、学習初期から身につけたい重要スキルです。

本記事では、Python初心者の方に向けてraiseの基本と実例、再送出の注意点まで丁寧に解説します。

Pythonのraiseとは?意図的に例外を発生させる基本

raiseの役割

raiseは、プログラムの実行を中断して「異常が起きた」ことを上位へ伝えるための文です。

外部からの入力や前提条件が守られていない場合に、明示的に例外を投げることで、バグの早期発見や原因箇所の特定が容易になります。

結果として、問題を曖昧に先送りするよりも堅牢なコードになります。

どんな時に使うのか

入力値が規定範囲外の場合や、未対応の機能が呼ばれた場合、または辞書のキーが存在しない場合など、想定通りに処理できない状況で使います。

「この先へ進むべきではない」ことを明示する合図だと考えてください。

最小の例

Python
# 意図的にValueErrorを発生させる最小の例
def positive_square_root(x: float) -> float:
    if x < 0:
        # 期待通りに計算できないので例外を投げる
        raise ValueError("xは0以上である必要があります")
    return x ** 0.5

print(positive_square_root(9))
print(positive_square_root(-1))  # ここで例外が発生
実行結果
3.0
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(positive_square_root(-1))
  File "...", line ..., in positive_square_root
    raise ValueError("xは0以上である必要があります")
ValueError: xは0以上である必要があります

基本構文(例外クラスとメッセージ)

構文はraise 例外クラス(エラーメッセージ)が基本です。

エラーメッセージは、原因がすぐ分かる具体的な文にします。

構文のバリエーション

  • 標準的: raise ValueError("理由")
  • 変数に格納した例外を投げる: err = ValueError("..."); raise err
  • 原因の例外を連鎖させる: raise RuntimeError("外側の説明") from inner_exc (後述)
Python
# 基本構文の例: 型と値の両方をチェック
def normalized_ratio(a: float, b: float) -> float:
    # 型チェック(ここではfloatまたはintを期待)
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        raise TypeError("aとbは数値(int/float)である必要があります")
    # 値チェック(分母は0不可)
    if b == 0:
        raise ValueError("b(分母)は0ではいけません")
    return a / b

print(normalized_ratio(3, 2))
print(normalized_ratio("3", 2))  # TypeError
実行結果
1.5
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(normalized_ratio("3", 2))  # TypeError
  File "...", line ..., in normalized_ratio
    raise TypeError("aとbは数値(int/float)である必要があります")
TypeError: aとbは数値(int/float)である必要があります

よくある誤り(文字列はraiseできない)

Python 3では文字列そのものをraiseできません

必ず例外クラス(例: ValueError)のインスタンスを投げます。

Python
# NG例: 文字列をraiseしてはいけない
def bad():
    raise "エラー"  # これはPython 3ではTypeErrorになります

bad()
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    bad()
  File "...", line ..., in bad
    raise "エラー"
TypeError: exceptions must derive from BaseException
Python
# OK例: 適切な例外クラスを使う
def good():
    raise ValueError("エラー: 入力が不正です")

good()
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    good()
  File "...", line ..., in good
    raise ValueError("エラー: 入力が不正です")
ValueError: エラー: 入力が不正です

例外クラスの選び方(初心者向け)

初心者のうちは、標準の例外クラスから適切なものを選ぶのが最短ルートです。

迷った時は「何が問題だったのか」を言い表す名前の例外を選び、説明的なメッセージを添えると伝わりやすくなります。

以下は代表的な選択基準です。

例外クラス使う場面例メッセージ
ValueError値の範囲や形式が不正“ageは0以上の整数である必要があります”
TypeError引数や戻り値の型が違う“pathはstr型である必要があります”
KeyError辞書や環境変数にキーがない“必須設定KEYが見つかりません: ‘API_KEY'”
NotImplementedError未対応・未実装の機能が呼ばれた“このフォーマットは未対応です: ‘xml'”
RuntimeError実行時の前提条件違反“接続前にsend()は呼べません”

ValueError(不正な値)

値の範囲や形式が不正な場合はValueErrorです。

例えば負の数や空文字など、型は正しいが中身が不正な時に使います。

Python
def parse_age(text: str) -> int:
    # 空文字や数字以外を弾く
    if not text or not text.isdigit():
        raise ValueError(f"ageの形式が不正です: {text!r}")
    age = int(text)
    if age < 0:
        raise ValueError("ageは0以上である必要があります")
    return age

print(parse_age("20"))
print(parse_age("abc"))  # ValueError
実行結果
20
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(parse_age("abc"))  # ValueError
  File "...", line ..., in parse_age
    raise ValueError(f"ageの形式が不正です: {text!r}")
ValueError: ageの形式が不正です: 'abc'

TypeError(型が違う)

期待した型と違う時はTypeErrorを使います。

型ヒントと組み合わせると原因が明確になります。

Python
from typing import Iterable

def head(items: Iterable, n: int = 1):
    if not isinstance(n, int):
        raise TypeError("nはint型である必要があります")
    return list(items)[:n]

print(head([1, 2, 3], n="2"))  # TypeError
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(head([1, 2, 3], n="2"))  # TypeError
  File "...", line ..., in head
    raise TypeError("nはint型である必要があります")
TypeError: nはint型である必要があります

KeyError(キーがない)

辞書や環境変数に欲しいキーがない場合はKeyErrorが適切です。

データ欠損を明確に示せます。

Python
settings = {"host": "localhost"}

def get_required(config: dict, key: str):
    if key not in config:
        raise KeyError(f"必須設定が不足しています: {key!r}")
    return config[key]

print(get_required(settings, "port"))  # KeyError
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(get_required(settings, "port"))  # KeyError
  File "...", line ..., in get_required
    raise KeyError(f"必須設定が不足しています: {key!r}")
KeyError: "必須設定が不足しています: 'port'"

NotImplementedError(未対応の機能)

未対応や未実装の機能が呼ばれたらNotImplementedErrorです。

注意: NotImplemented(特殊な値)とNotImplementedError(例外)は別物です。

Python
class Exporter:
    def export(self, fmt: str) -> str:
        if fmt == "json":
            return '{"ok": true}'
        # まだ対応していない形式
        raise NotImplementedError(f"このフォーマットは未対応です: {fmt!r}")

exp = Exporter()
print(exp.export("xml"))  # NotImplementedError
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(exp.export("xml"))  # NotImplementedError
  File "...", line ..., in export
    raise NotImplementedError("このフォーマットは未対応です: 'xml'")
NotImplementedError: このフォーマットは未対応です: 'xml'

RuntimeError(前提条件を満たさない)

操作の順序や状態が不適切な時はRuntimeErrorが選択肢です。

接続前の送信など、値や型の問題ではない「状態」の問題に向いています。

Python
class Connection:
    def __init__(self):
        self.connected = False

    def connect(self):
        self.connected = True

    def send(self, data: bytes):
        if not self.connected:
            raise RuntimeError("接続前にsend()は呼べません")
        print("送信:", data)

conn = Connection()
conn.send(b"hello")  # RuntimeError
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    conn.send(b"hello")  # RuntimeError
  File "...", line ..., in send
    raise RuntimeError("接続前にsend()は呼べません")
RuntimeError: 接続前にsend()は呼べません

実例で学ぶraiseの使い方

入力チェックでValueErrorをraise

関数の入口でガードして、誤入力を早期に検出します。

後段でおかしな挙動になるより、入口で止める方が安全です。

Python
def percentage(text: str) -> float:
    """'85%'のような文字列を0.0〜1.0の小数へ変換"""
    if not text.endswith("%"):
        raise ValueError("末尾に%が必要です (例: '85%')")
    num = text[:-1]
    if not num.replace(".", "", 1).isdigit():
        raise ValueError(f"数値部分が不正です: {num!r}")
    value = float(num) / 100.0
    if not (0.0 <= value <= 1.0):
        raise ValueError("0%〜100%の範囲で指定してください")
    return value

print(percentage("85%"))
print(percentage("120%"))  # 範囲外 → ValueError
実行結果
0.85
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(percentage("120%"))  # 範囲外 → ValueError
  File "...", line ..., in percentage
    raise ValueError("0%〜100%の範囲で指定してください")
ValueError: 0%〜100%の範囲で指定してください

未対応の処理でNotImplementedErrorをraise

ロードマップ上で未対応の分岐を明示すると、呼び出し側は安全にフォールバックできます。

Python
def export(data: dict, fmt: str) -> str:
    if fmt == "json":
        import json
        return json.dumps(data)
    elif fmt == "csv":
        # TODO: 後で実装
        raise NotImplementedError("csv出力はまだ未対応です")
    else:
        raise NotImplementedError(f"未知の形式です: {fmt!r}")

print(export({"x": 1}, "csv"))
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(export({"x": 1}, "csv"))
  File "...", line ..., in export
    raise NotImplementedError("csv出力はまだ未対応です")
NotImplementedError: csv出力はまだ未対応です

ラッパー関数でKeyErrorをraise

外部APIや辞書アクセスのラッパーで欠損キーをKeyErrorで伝えると、呼び出し側は例外種別で分岐できます。

Python
import os

def get_env_required(name: str) -> str:
    """必須な環境変数を取得。なければKeyErrorを投げる。"""
    try:
        return os.environ[name]
    except KeyError as e:
        # 独自メッセージに差し替えて再送出
        raise KeyError(f"必須の環境変数が見つかりません: {name!r}") from e

print(get_env_required("API_KEY"))  # 未設定ならKeyError
実行結果
Traceback (most recent call last):
  File "...", line ..., in get_env_required
    return os.environ[name]
KeyError: 'API_KEY'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "...", line ..., in <module>
    print(get_env_required("API_KEY"))  # 未設定ならKeyError
  File "...", line ..., in get_env_required
    raise KeyError(f"必須の環境変数が見つかりません: {name!r}") from e
KeyError: "必須の環境変数が見つかりません: 'API_KEY'"

Noneの返却ではなく例外で失敗を示す

失敗をNoneで返すと、バグに気づきにくくなります

失敗は例外で明確に伝える方が安全です。

サンプルコード(悪い例と良い例)

Python
# 悪い例: 失敗時にNoneを返す
def parse_int_or_none(text: str):
    return int(text) if text.isdigit() else None

x = parse_int_or_none("abc")
# xがNoneだとこの後の処理でTypeErrorなどの二次被害を生むことがある
print(x + 1)  # ここでエラー: TypeError
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    print(x + 1)
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
Python
# 良い例: 失敗時は明確にValueErrorを投げる
def parse_int(text: str) -> int:
    if not text.isdigit():
        raise ValueError(f"整数として解釈できません: {text!r}")
    return int(text)

# 呼び出し側はtry-exceptで意図を持って対処できる
try:
    print(parse_int("abc"))
except ValueError as e:
    print("入力エラー:", e)
実行結果
入力エラー: 整数として解釈できません: 'abc'

例外の再送出(re-raise)と注意点

try-except内でraiseだけを書く

捕捉した例外をそのまま上に投げ直すときはraiseだけを書くのが基本です。

これにより元のトレースバック(発生箇所)が保たれます。

Python
def outer():
    try:
        int("abc")  # ここでValueErrorが発生
    except ValueError:
        # ローカルでは何もせず、そのまま再送出
        raise

outer()
実行結果
Traceback (most recent call last):
  File "...", line ..., in outer
    int("abc")  # ここでValueErrorが発生
ValueError: invalid literal for int() with base 10: 'abc'

ログを記録してから再送出

ログを残しつつ例外を上位へ伝えたいときはlogging.exception()raiseを組み合わせます。

logging.exception()はexcept節内で使うとスタックトレース付きでERRORログを出力します。

Python
import logging

logging.basicConfig(level=logging.ERROR, format="%(levelname)s:%(message)s")
logger = logging.getLogger(__name__)

def work():
    try:
        return 10 / 0
    except Exception:
        logger.exception("計算に失敗しました")
        raise  # 元の例外(ZeroDivisionError)を保ったまま再送出

work()
実行結果
ERROR:計算に失敗しました
Traceback (most recent call last):
  File "...", line ..., in work
    return 10 / 0
ZeroDivisionError: division by zero

Traceback (most recent call last):
  File "...", line ..., in <module>
    work()
  File "...", line ..., in work
    raise  # 元の例外(ZeroDivisionError)を保ったまま再送出
ZeroDivisionError: division by zero

例外情報を失わないコツ

「どこで何が起きたか」を失わないことが、デバッグ効率を大きく左右します。

特にraise eは元のトレースバックを失うので注意してください。

1) そのまま再送出する時はraiseのみ

Python
def re_raise():
    try:
        float("NaN!")  # ValueError
    except ValueError:
        raise  # OK: 元のトレースバックを保持

2) NGパターン: raise e はトレースバックが現在位置にリセットされる

Python
def lose_traceback():
    try:
        float("NaN!")  # ここが本当の発生源
    except ValueError as e:
        raise e  # NG: トレースバックがこの行から始まってしまう

実行結果(概念的には「発生源の行」が表示されにくくなる)ため、原因特定が難しくなります。

3) 新しい文脈を与える時は raise NewError(…) from e

raise 新しい例外 from 元の例外で「原因の例外」を連鎖できます。

これにより、「上位の文脈の説明」と「下位の真の原因」の両方が一つのトレースバックに含まれ、解析が容易です。

Python
def wrap_with_context():
    try:
        int("xyz")  # ValueError
    except ValueError as e:
        # より上位の文脈を説明する例外に包んで連鎖
        raise RuntimeError("ユーザーIDの解析に失敗しました") from e

wrap_with_context()
実行結果
Traceback (most recent call last):
  File "...", line ..., in wrap_with_context
    int("xyz")  # ValueError
ValueError: invalid literal for int() with base 10: 'xyz'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "...", line ..., in <module>
    wrap_with_context()
  File "...", line ..., in wrap_with_context
    raise RuntimeError("ユーザーIDの解析に失敗しました") from e
RuntimeError: ユーザーIDの解析に失敗しました

4) 内部原因を隠したい時は from None

外部に内部の実装詳細を漏らしたくない場合は、raise SomeError("公開メッセージ") from Noneで原因の連鎖表示を抑止できます。

Python
def hide_internal():
    try:
        open("/root/secret.txt")
    except OSError:
        # 内部のパスなどを隠して公開用のメッセージに限定
        raise PermissionError("許可されていない操作です") from None

hide_internal()
実行結果
Traceback (most recent call last):
  File "...", line ..., in <module>
    hide_internal()
  File "...", line ..., in hide_internal
    raise PermissionError("許可されていない操作です") from None
PermissionError: 許可されていない操作です

まとめると:

  • そのまま再送出 → raise だけ
  • 文脈を付けて伝える → raise NewError(...) from e
  • 原因を隠す → from None
  • 避けるべき → raise e

まとめ

raiseは「この条件では処理を続けられない」という意図をコードで正確に表現するための基礎テクニックです。

値が不正ならValueError、型が違えばTypeError、キーがなければKeyError、未対応はNotImplementedError、状態不整合ならRuntimeErrorと、適切な例外を選ぶことで、原因が読み取れるエラーメッセージを実現できます。

失敗をNoneでごまかさず、例外で明確に伝える姿勢が品質を高めます。

再送出ではraiseだけで元のトレースバックを保ち、文脈追加はraise ... from eを活用しましょう。

今日から小さな関数でもraiseを積極的に使い、読みやすく堅牢なコードへと改善していってください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

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

URLをコピーしました!