Pythonの学習を始めると、プログラムが途中で止まってしまう実行時エラーに必ず出会います。
この記事では、例外(Exception)とは何か、構文エラーとの違い、よくあるエラーの実例、そして内部で何が起きているのかをやさしく説明します。
エラーメッセージを落ち着いて読み解く力を身につけましょう。
Pythonの例外とは(実行時エラーの基本)
例外(Exception)の意味と役割
例外(Exception)とは、プログラムの実行中に予期しない事態が起こったことを伝える仕組みです。
具体的には、ゼロで割る、存在しないファイルを開く、型が合わない操作をするなどの状況で発生します。
Pythonはその場で通常の処理を中断し、エラーの種類や発生位置を示す情報(トレースバック)を出力します。
例外は単なるメッセージではなくオブジェクト(値)です。
発生した例外オブジェクトは、呼び出し元へと伝播し、どこかで対処されない限りプログラムを停止させます。
これにより、異常な状態のまま処理を続ける危険を防ぎます。
例外の役割を一言でいうと
例外は「異常を即座に知らせて安全に止める」ための信号です。
後続の処理が破綻しないように、早めにエラーを表面化させます。
実行中に何が起きるか(エラー検出から停止まで)
例外が起きたときの流れは概ね次のとおりです。
内部動作を知ると、なぜ途中まで出力されてから止まるのかが理解しやすくなります。
- 実行中の命令がエラー条件を検出します(例:
int('abc')
が変換不能)。 - 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行も実行される前に止まるため、実行時エラーとは発生タイミングが異なります。
# コロン(:)が必要な位置で欠けている例
if True
print("OK")
File "syntax_error.py", line 2
if True
^
SyntaxError: expected ':'
実行時エラーは実行中に起きる
実行時エラー(例外)は、文法としては正しいが、実際に動かしてみると成り立たない状況で発生します。
例えばゼロ除算や未定義変数の参照などです。
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
を送出します。
整数同士でも浮動小数点でも同じです。
# ゼロ除算の典型例
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
になります。
スペルミスやスコープの勘違いがよくある原因です。
# スペルミスで未定義の変数を参照している例
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()
を適用するとエラーです。
# 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
になります。
# 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
に対してメソッドを呼んでしまうのは初心者がよく踏む落とし穴です。
# 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 | 分母が0 | 1/0 |
NameError | 未定義名の参照 | print(xxx) |
TypeError | 型に合わない操作 | len(10) |
KeyError | 無い辞書キー | user['email'] |
IndexError | 範囲外アクセス | lst[10] |
AttributeError | 無い属性アクセス | None.upper() |
例外の仕組みをやさしく理解
例外の伝播と未処理例外
例外は「今の関数で処理されなければ呼び出し元へ」と階段を上るように伝播します。
最終的にどこでも処理されなければプログラムは終了します。
ミニサンプル(関数間の伝播)
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
を継承しています。
一方、KeyboardInterrupt
やSystemExit
など、BaseException
直下にある特別な例外も存在します。
代表的な階層のイメージ
親クラス | 主な子クラス(例) |
---|---|
BaseException | SystemExit, KeyboardInterrupt, GeneratorExit, Exception |
Exception | ArithmeticError, LookupError, OSError, ValueError, TypeError, AttributeError, RuntimeError など |
ArithmeticError | ZeroDivisionError など |
LookupError | KeyError, IndexError |
関係をコードで確かめる
# 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の例(ユーザーが中断)
# このスクリプトを実行中に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の例(明示的な終了)
# sys.exit()はSystemExitを送出します。終了コード0は正常終了を意味します。
import sys
print("before exit")
sys.exit(0) # ここでプログラムが終了
print("after exit") # 実行されません
before exit
KeyboardInterruptやSystemExitはExceptionではなくBaseExceptionの直下です。
したがって、一般的なexcept Exception
では捕捉されません。
これは、ユーザーの中断や明示的な終了を誤って握りつぶさないための設計です。
まとめ
例外は、実行中に起きた異常を安全に伝えるためのオブジェクトによる信号です。
構文エラーは実行前に止まり、実行時エラーは実行中に止まるという違いをまず押さえましょう。
初心者がよく遭遇するZeroDivisionError
、NameError
、TypeError
、KeyError
/IndexError
、AttributeError
は、原因パターンが明確です。
トレースバックを上から下に読み、どこで何が起きたかを落ち着いて特定しましょう。
さらに、例外の伝播とクラス階層を知っておくと、エラーメッセージの意味が一段と明確になります。
最後にKeyboardInterruptやSystemExitのような特別な例外はException
とは扱いが異なる点も忘れずに心に留めておくと安心です。
次のステップとして、別記事でtry
–except
やfinally
などの具体的なエラー処理方法を学ぶと、プログラムをより堅牢にできます。