概要(デバッグは「原因を特定して直すための再現と観察」)
デバッグの核心は、問題を確実に再現し、プログラムの状態を正しく観察して、原因に最短で辿り着くことです。初心者は「再現条件を固定する」「トレースバック(エラーメッセージ)を読む」「最小例に切り詰める」「変数の中身と処理の流れを可視化する」を型として身につけると、作業が劇的に楽になります。
再現から始める(条件を固定して、最小例へ切り詰める)
再現条件の固定
エラーは毎回同じ入力・設定で再現できる状態にします。乱数・現在時刻・外部APIレスポンスなど、結果が変わる要素は「固定値」に置き換えます。これにより、修正の効果を正しく検証できます。
最小再現例を作る
問題に関係ないコードは削ぎ落とし、数十行以内で同じ不具合が出る「最小例」を作ります。余計な依存がない方が、原因が浮かび上がります。最小例は、質問やバグ報告にも使える「強い武器」になります。
トレースバック(エラー表示)を読む(どこで何が起きたか)
どの行で、どんな種類のエラーか
Pythonのトレースバックは、最後の行が「エラーの種類」と「一言の説明」、その直前までが「どのファイルの何行目で起きたか」を示します。まず“最も下の原因行”を開いて、そこにある変数の値と前提が期待どおりかを確認します。
def div(a, b):
return a / b
print(div(1, 0))
Python実行例(読み方の要点):
- ZeroDivisionError(0で割っている)
- example.py の div 関数の行で発生 まず b の値が想定どおりか、ゼロを許してよい仕様かを検討します。
観察の方法(print → logging → デバッガの段階的活用)
まずは print で「どこで何が入っているか」を見る
最短で効果があるのは、疑わしい箇所に print を挿入し、入力値・分岐結果・返り値を出す方法です。多用すると散らかるので、原因が絞れたら削除するか、logging に置き換えます。
def fetch_price(tax_rate, price):
print("DEBUG:", tax_rate, price) # 一時的な観察
return price * (1 + tax_rate)
Pythonlogging に切り替えて「綺麗に記録する」
print は一時観察、logging は運用・原因追跡向けです。レベルとフォーマットを使い、時刻や行番号付きで記録します。例外は logger.exception でスタックトレースごと残します。
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(lineno)d %(message)s")
try:
1/0
except Exception:
logging.exception("計算に失敗")
Pythonデバッガで止めて、覗き、1行ずつ進める
ブレークポイント(止めたい行の指定)→実行→停止中に変数を確認→ステップ実行(1行ずつ進める)という流れで、「想定と現実の差」を正確に把握します。条件付きブレークポイント(特定の値の時だけ止める)を使うと、再現が難しいバグも楽になります。
標準デバッガ pdb(最小のツールで確実に原因へ)
最短の使い方
疑わしい行にブレークポイントを置き、実行中に止めて観察します。
def total(items):
import pdb; pdb.set_trace() # ここで停止
s = 0
for x in items:
s += x
return s
print(total([1, 2, 3]))
Python停止中に使う基本コマンド(代表例):
- n(next): 1行進める
- s(step): 関数の中へ入る
- c(continue): 次のブレークポイントまで進める
- p 変数名: 変数の中身を表示
- l(list): 近辺のソース表示
条件付きで止める
特定のケースだけ止めたい時は、分岐の中へ置くか、条件を満たしたら set_trace を呼ぶ設計にします。大量ループで全部止まるのを避けられます。
思考の型(仮説→観察→反証→修正のループ)
仮説を立てて、最短観察で反証する
「ここで None が入っている」「この分岐は常に false」など、1つの仮説に絞って観察(print/log/デバッガ)で確認します。外れたら次の仮説へ。複数仮説を同時に追うと、観察が散らかり時間を浪費します。
修正は小さく、直後に再現テスト
1行〜数行の変更に留め、再現条件で直ちに再実行します。修正の副作用を避けるため、広範囲の書き換えはしないのが鉄則です。
例題で身につける(入力・分岐・外部連携の定番バグ)
例題1:型ズレで期待どおりに計算されない
def calc_total(price, tax_rate):
return price * (1 + tax_rate)
print(calc_total("1000", 0.1)) # 文字列 * float → 型ズレ
Python観察ポイント:
- 入力の型を print で出す(type(price))
- 変換を入れる(int(price))か、早期バリデーションで弾く
修正例:
def calc_total(price, tax_rate):
if not isinstance(price, (int, float)):
raise TypeError("priceは数値を指定してください")
return price * (1 + tax_rate)
Python例題2:分岐の条件が常に意図と逆に評価される
def is_premium(user):
return user.get("score", 0) > 100 # 100以上がプレミアムのつもり
u = {"score": 100}
print(is_premium(u)) # False → 条件の境界ミス
Python観察ポイント:
- 条件の境界(>= か > か)を明示し、テストに境界値を必ず含める
修正例:
def is_premium(user):
return user.get("score", 0) >= 100
Python例題3:外部APIの失敗が静かに飲み込まれる
import requests
def fetch_user(uid):
r = requests.get(f"https://httpbin.org/status/500")
if r.status_code == 200:
return r.json()
return None # 失敗理由が分からない
Python観察ポイント:
- 例外を出す(raise_for_status)
- 失敗時にログへ理由を残す
修正例:
import logging, requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
def fetch_user(uid):
try:
r = requests.get(f"https://httpbin.org/get", timeout=5)
r.raise_for_status()
return r.json()
except requests.RequestException as e:
logging.error("API失敗: %s", e)
return None
Python##落とし穴の回避(例外処理・ミュータブル・環境差)
例外を広く握りつぶさない
except Exception: pass のような記述は原因を隠します。最低でもエラーメッセージを記録し、必要な範囲の例外型だけを捕捉します。
ミュータブルなデフォルト引数に注意
関数定義で list や dict をデフォルトにすると、呼び出し間で共有されて意図せず蓄積します。None を使って中で生成するのが安全です。
環境差(バージョン・依存)を固定する
仮想環境で同じ依存を使い、requirements.txtで固定します。環境が違うと、同じコードでも再現しません。まず環境を可視化し、ズレをなくすことが近道です。
まとめ(“再現→観察→小さく修正”を型にする)
デバッグは「再現を固め、観察して、最小の修正を試す」作業です。トレースバックで原因行へ直行し、print→logging→デバッガの順で可視化を深める。仮説はひとつずつ検証して反証し、修正直後に再現テストを回す。例外は記録し、ミュータブルな罠を避け、環境差を固定する。この型を体に入れれば、初心者でも短い手順で原因へ辿り着けるようになります。
