閉じる

Pythonの例外(Exception)入門: 実行時エラーの正しい理解

Pythonの学習を始めると、プログラムが途中で止まってしまう実行時エラーに必ず出会います。

この記事では、例外(Exception)とは何か、構文エラーとの違い、よくあるエラーの実例、そして内部で何が起きているのかをやさしく説明します。

エラーメッセージを落ち着いて読み解く力を身につけましょう。

Pythonの例外とは(実行時エラーの基本)

例外(Exception)の意味と役割

例外(Exception)とは、プログラムの実行中に予期しない事態が起こったことを伝える仕組みです。

具体的には、ゼロで割る、存在しないファイルを開く、型が合わない操作をするなどの状況で発生します。

Pythonはその場で通常の処理を中断し、エラーの種類や発生位置を示す情報(トレースバック)を出力します。

例外は単なるメッセージではなくオブジェクト(値)です。

発生した例外オブジェクトは、呼び出し元へと伝播し、どこかで対処されない限りプログラムを停止させます。

これにより、異常な状態のまま処理を続ける危険を防ぎます。

例外の役割を一言でいうと

例外は「異常を即座に知らせて安全に止める」ための信号です。

後続の処理が破綻しないように、早めにエラーを表面化させます。

実行中に何が起きるか(エラー検出から停止まで)

例外が起きたときの流れは概ね次のとおりです。

内部動作を知ると、なぜ途中まで出力されてから止まるのかが理解しやすくなります。

  1. 実行中の命令がエラー条件を検出します(例: int('abc')が変換不能)。
  2. Pythonが該当する例外オブジェクトを生成して「送出」します。
  3. その関数内で対処されないと、呼び出し元へ伝播します(スタックの巻き戻し)。
  4. どこでも対処されない場合、トレースバックを表示してプロセスを終了します。

ミニサンプル(途中まで出力される理由)

Python
# 実行途中で例外が起きると、そこまでのprintは出力されます
def parse_age(text: str) -> int:
    # 数字でない文字列はint変換できないためValueErrorになります
    return int(text)

print("開始")
age = parse_age("abc")  # ここでValueError
print("この行は実行されません")
実行結果
開始
Traceback (most recent call last):
  File "example.py", line 8, in <module>
    age = parse_age("abc")
  File "example.py", line 5, in parse_age
    return int(text)
ValueError: invalid literal for int() with base 10: 'abc'

構文エラーとの違い(いつ発生するか)

構文エラー(SyntaxError)は実行前

構文エラーは、そもそもPythonの文法に従っていないコードに対して起きます。

プログラムは1行も実行される前に止まるため、実行時エラーとは発生タイミングが異なります。

Python
# コロン(:)が必要な位置で欠けている例
if True
    print("OK")
実行結果
  File "syntax_error.py", line 2
    if True
           ^
SyntaxError: expected ':'

実行時エラーは実行中に起きる

実行時エラー(例外)は、文法としては正しいが、実際に動かしてみると成り立たない状況で発生します。

例えばゼロ除算や未定義変数の参照などです。

Python
print("start")
x = 1 / 0  # ここでZeroDivisionError
print("end")  # 実行されません
実行結果
start
Traceback (most recent call last):
  File "runtime_error.py", line 2, in <module>
    x = 1 / 0
ZeroDivisionError: division by zero

例外が発生する典型例(初心者向け)

ZeroDivisionError(ゼロで割った)

ゼロ除算は数学的に未定義のため、PythonはZeroDivisionErrorを送出します。

整数同士でも浮動小数点でも同じです。

Python
# ゼロ除算の典型例
numerator = 10
denominator = 0
result = numerator / denominator  # ここでZeroDivisionError
実行結果
Traceback (most recent call last):
  File "zdiv.py", line 4, in <module>
    result = numerator / denominator
ZeroDivisionError: division by zero

ヒント

除算の前に分母が0でないことを必ず確認します。

入力値から計算する場合は注意が必要です。

NameError(変数名が未定義)

未定義の変数名を参照するとNameErrorになります。

スペルミスやスコープの勘違いがよくある原因です。

Python
# スペルミスで未定義の変数を参照している例
count = 3
print(cnt)  # 'count'のつもりが'cnt'
実行結果
Traceback (most recent call last):
  File "name_error.py", line 3, in <module>
    print(cnt)
NameError: name 'cnt' is not defined

ヒント

変数名の綴りを確認し、定義の位置(スコープ)が正しいかも見直します。

TypeError(型が合わない)

操作対象の型が期待と違う場合にTypeErrorが起きます。

例えば、長さを持たない整数にlen()を適用するとエラーです。

Python
# len()はコレクションや文字列などの「長さ」を持つ対象に使います
value = 10
print(len(value))  # intには長さがありません
実行結果
Traceback (most recent call last):
  File "type_error.py", line 3, in <module>
    print(len(value))
TypeError: object of type 'int' has no len()

ヒント

エラーメッセージに出る型名に注目し、今その型に対して妥当な操作かを確認します。

KeyError/IndexError(存在しないキー/範囲外)

辞書で存在しないキーを取り出すとKeyError、リストで範囲外のインデックスを参照するとIndexErrorになります。

Python
# KeyErrorの例
user = {"name": "Taro", "age": 20}
print(user["email"])  # 'email'キーは存在しません

# IndexErrorの例
nums = [1, 2, 3]
print(nums[3])  # 有効なインデックスは0,1,2です
実行結果
Traceback (most recent call last):
  File "lookup_error.py", line 3, in <module>
    print(user["email"])
KeyError: 'email'
Traceback (most recent call last):
  File "lookup_error.py", line 7, in <module>
    print(nums[3])
IndexError: list index out of range

ヒント

  • 辞書: in演算子やdict.get()で存在確認してから参照します。
  • リスト: 長さlen(nums)とインデックスの関係を見直します。

AttributeError(属性がない)

オブジェクトが持たないメソッドや属性にアクセスするとAttributeErrorです。

特にNoneに対してメソッドを呼んでしまうのは初心者がよく踏む落とし穴です。

Python
# Noneには文字列メソッドはありません
text = None
print(text.upper())  # この呼び出しはできません
実行結果
Traceback (most recent call last):
  File "attr_error.py", line 3, in <module>
    print(text.upper())
AttributeError: 'NoneType' object has no attribute 'upper'

ヒント

値がNoneになっていないか、処理の前に確認しましょう。

関数が何を返すか(戻り値)の仕様も要チェックです。

代表的な例外の早見表

例外名典型的な原因
ZeroDivisionError分母が01/0
NameError未定義名の参照print(xxx)
TypeError型に合わない操作len(10)
KeyError無い辞書キーuser['email']
IndexError範囲外アクセスlst[10]
AttributeError無い属性アクセスNone.upper()

例外の仕組みをやさしく理解

例外の伝播と未処理例外

例外は「今の関数で処理されなければ呼び出し元へ」と階段を上るように伝播します。

最終的にどこでも処理されなければプログラムは終了します。

ミニサンプル(関数間の伝播)

Python
def c():
    # ここでValueErrorが発生
    return int("abc")  # 変換できない

def b():
    return c()  # cの例外はbでは処理されません

def a():
    return b()  # aでも処理されないため、最上位へ伝播します

a()  # 未処理例外としてプログラムが停止
実行結果
Traceback (most recent call last):
  File "propagate.py", line 11, in <module>
    a()
  File "propagate.py", line 9, in a
    return b()
  File "propagate.py", line 6, in b
    return c()
  File "propagate.py", line 3, in c
    return int("abc")
ValueError: invalid literal for int() with base 10: 'abc'

上から下に読むと、どの関数のどの行で落ちたかを追跡できます。

トレースバックはデバッグの最重要手がかりです。

例外クラスの階層(ExceptionとBaseException)

Pythonの例外はクラス階層で整理されています。

多くの実行時エラーはExceptionを継承しています。

一方、KeyboardInterruptSystemExitなど、BaseException直下にある特別な例外も存在します。

代表的な階層のイメージ

親クラス主な子クラス(例)
BaseExceptionSystemExit, KeyboardInterrupt, GeneratorExit, Exception
ExceptionArithmeticError, LookupError, OSError, ValueError, TypeError, AttributeError, RuntimeError など
ArithmeticErrorZeroDivisionError など
LookupErrorKeyError, IndexError

関係をコードで確かめる

Python
# issubclassで階層関係を確認できます
print(issubclass(ZeroDivisionError, ArithmeticError))  # True
print(issubclass(ArithmeticError, Exception))          # True
print(issubclass(KeyboardInterrupt, Exception))        # False
print(issubclass(KeyboardInterrupt, BaseException))    # True
実行結果
True
True
False
True

多くの例外はExceptionに含まれますが、すべてではありません

これが次の特別な例外の振る舞いにつながります。

特別な例外(KeyboardInterrupt/SystemExit)

KeyboardInterrupt(Ctrl+C)とSystemExit(sys.exit())はBaseException直下で、通常のエラーと少し扱いが異なります。

プログラムの「外側の事情」で停止するイメージを持つと理解しやすいです。

KeyboardInterruptの例(ユーザーが中断)

Python
# このスクリプトを実行中にCtrl+Cを押すとKeyboardInterruptが発生します
import time

print("5秒スリープ中... (Ctrl+Cで中断)")
time.sleep(5)  # ここで中断を受けると例外が送出されます
print("この行は中断しなければ表示されます")
実行結果
5秒スリープ中... (Ctrl+Cで中断)
Traceback (most recent call last):
  File "kbdint.py", line 6, in <module>
    time.sleep(5)
KeyboardInterrupt

SystemExitの例(明示的な終了)

Python
# sys.exit()はSystemExitを送出します。終了コード0は正常終了を意味します。
import sys

print("before exit")
sys.exit(0)  # ここでプログラムが終了
print("after exit")  # 実行されません
実行結果
before exit
注意

KeyboardInterruptやSystemExitはExceptionではなくBaseExceptionの直下です。

したがって、一般的なexcept Exceptionでは捕捉されません。

これは、ユーザーの中断や明示的な終了を誤って握りつぶさないための設計です。

まとめ

例外は、実行中に起きた異常を安全に伝えるためのオブジェクトによる信号です。

構文エラーは実行前に止まり、実行時エラーは実行中に止まるという違いをまず押さえましょう。

初心者がよく遭遇するZeroDivisionErrorNameErrorTypeErrorKeyError/IndexErrorAttributeErrorは、原因パターンが明確です。

トレースバックを上から下に読み、どこで何が起きたかを落ち着いて特定しましょう。

さらに、例外の伝播とクラス階層を知っておくと、エラーメッセージの意味が一段と明確になります。

最後にKeyboardInterruptSystemExitのような特別な例外はExceptionとは扱いが異なる点も忘れずに心に留めておくと安心です。

次のステップとして、別記事でtryexceptfinallyなどの具体的なエラー処理方法を学ぶと、プログラムをより堅牢にできます。

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

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

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

URLをコピーしました!