Python Debugger (pdb)
デバッグツール
Python Debugger (pdb)
概要
Python Debugger(pdb)は、Pythonの標準デバッガーです。コマンドライン操作でPythonプログラムの実行を制御し、ブレークポイント設定、変数の確認、ステップ実行が可能です。
詳細
Python Debugger(pdb)は、Pythonの標準ライブラリに含まれる対話型デバッガーで、1990年代からPython言語と共に発展してきました。Pythonインタープリターに組み込まれているため、追加のインストールは不要で、シンプルなコマンドライン インターフェースを通じて強力なデバッグ機能を提供します。pdbは、GDBのPythonポートとして設計され、類似のコマンド体系を持ちながらPythonの動的な性質に最適化されています。
pdbの特徴は、実行中のPythonプログラムに対する完全なインタラクティブ制御です。任意のポイントでプログラムの実行を一時停止し、変数の値確認、関数呼び出し、式の評価、コールスタックの検査が可能です。post-mortem デバッグ機能により、例外発生後にプログラムの状態を詳細に分析できます。また、リモートデバッグ機能を使用して、別のプロセスやネットワーク越しのPythonプログラムをデバッグすることも可能です。
IDEの高機能デバッガーが普及する中でも、pdbは軽量性とシンプルさで根強い支持を得ています。特に、本番環境やリモートサーバーでの問題調査、CIパイプラインでのデバッグ、Dockerコンテナ内でのトラブルシューティングなど、GUI環境が利用できない状況で威力を発揮します。Python 3.7以降ではbreakpoint()
ビルトイン関数が追加され、pdbの利用がさらに簡素化されました。
pdb++(pdbpp)やIPDBなど、pdbを拡張したサードパーティツールも存在し、構文ハイライト、オートコンプリート、改善されたユーザーインターフェースを提供しています。現在のPython開発において、基本ツールとしての地位を保ち続けています。
メリット・デメリット
メリット
- 標準搭載: Pythonインストール時から利用可能、追加設定不要
- 軽量性: 最小限のリソース消費でデバッグ実行
- シンプルな操作: 直感的なコマンドライン インターフェース
- 完全な制御: プログラム実行の詳細な制御と検査
- post-mortem分析: 例外発生後の詳細な状態分析
- リモートデバッグ: ネットワーク経由でのデバッグ可能
- IDEに依存しない: 任意の環境で一貫したデバッグ体験
- 拡張性: カスタムコマンドとスクリプト機能
デメリット
- 学習コスト: コマンドライン操作の習得が必要
- GUI不足: 視覚的なインターフェースの欠如
- 機能制限: IDEデバッガーと比較して機能が限定的
- 効率性: 大規模プログラムでの操作効率の低下
- 表示制限: 複雑なデータ構造の可視化が困難
- 統合不足: 現代的な開発ツールとの統合が弱い
- 初心者の敷居: Python初心者には操作が困難
主要リンク
- Python pdb公式ドキュメント
- Python Debugging With Pdb
- pdb++(拡張版pdb)
- IPDB(IPython統合版)
- Python デバッグ チュートリアル
- pdb コマンドリファレンス
書き方の例
pdbの基本的な起動方法
# 方法1: プログラム開始時にpdbで起動
# python -m pdb script.py
# 方法2: Python 3.7以降のbreakpoint()関数
def problematic_function(data):
result = []
for item in data:
breakpoint() # ここでデバッガー起動
processed = item * 2
result.append(processed)
return result
# 方法3: 従来のpdb.set_trace()
import pdb
def another_function():
x = 10
y = 20
pdb.set_trace() # ここでデバッガー起動
z = x + y
return z
# 方法4: post-mortem デバッグ
try:
# エラーが発生する処理
result = 1 / 0
except:
import pdb
pdb.post_mortem() # 例外発生時点の状態でデバッグ開始
基本的なpdbコマンド
# デバッグ対象のサンプルコード
def calculator(operation, a, b):
if operation == 'add':
return a + b
elif operation == 'subtract':
return a - b
elif operation == 'multiply':
return a * b
elif operation == 'divide':
if b == 0:
raise ValueError("Division by zero")
return a / b
else:
raise ValueError("Unknown operation")
def main():
operations = [
('add', 10, 5),
('subtract', 10, 3),
('multiply', 4, 7),
('divide', 10, 0) # エラーが発生する
]
for op, x, y in operations:
breakpoint() # 各反復でデバッグ
try:
result = calculator(op, x, y)
print(f"{op}({x}, {y}) = {result}")
except ValueError as e:
print(f"Error: {e}")
# pdbコマンド例:
# (Pdb) l # 現在の行周辺のコード表示
# (Pdb) n # 次の行へ(ステップオーバー)
# (Pdb) s # ステップイン(関数内に入る)
# (Pdb) c # 実行継続
# (Pdb) p op # 変数opの値表示
# (Pdb) pp operations # 変数operationsの整形表示
# (Pdb) w # 現在のスタックフレーム表示
# (Pdb) u # 上位スタックフレームに移動
# (Pdb) d # 下位スタックフレームに移動
高度なpdbコマンドの使用
class DataProcessor:
def __init__(self, data):
self.data = data
self.processed_count = 0
def process_items(self):
results = []
for i, item in enumerate(self.data):
# 条件付きブレークポイント設定例
if i == 2:
breakpoint()
processed = self._process_single_item(item)
results.append(processed)
self.processed_count += 1
return results
def _process_single_item(self, item):
if isinstance(item, str):
return item.upper()
elif isinstance(item, (int, float)):
return item ** 2
else:
return str(item)
# 高度なpdbコマンド例:
# (Pdb) break DataProcessor.process_items # メソッドにブレークポイント
# (Pdb) break 15, i > 1 # 条件付きブレークポイント(15行目、i > 1の時のみ)
# (Pdb) tbreak 20 # 一時的ブレークポイント(一度だけ停止)
# (Pdb) enable/disable 1 # ブレークポイント1の有効/無効切り替え
# (Pdb) clear # 全ブレークポイント削除
# (Pdb) ignore 1 2 # ブレークポイント1を2回無視
processor = DataProcessor(['hello', 42, 'world', 3.14, None])
変数検査とコード実行
def complex_calculation(data_list):
import math
results = {
'sum': 0,
'mean': 0,
'std_dev': 0,
'processed_items': []
}
# 数値のみフィルタリング
numeric_data = [x for x in data_list if isinstance(x, (int, float))]
breakpoint() # 検査ポイント
if not numeric_data:
return results
# 統計計算
total = sum(numeric_data)
mean = total / len(numeric_data)
variance = sum((x - mean) ** 2 for x in numeric_data) / len(numeric_data)
std_dev = math.sqrt(variance)
results.update({
'sum': total,
'mean': mean,
'std_dev': std_dev,
'processed_items': numeric_data
})
return results
# pdbでの変数検査コマンド:
# (Pdb) p data_list # リスト内容表示
# (Pdb) pp results # 辞書の整形表示
# (Pdb) len(numeric_data) # 式の評価
# (Pdb) [x for x in data_list if x > 10] # リスト内包表記の実行
# (Pdb) type(data_list[0]) # 型確認
# (Pdb) dir(math) # モジュール属性一覧
# (Pdb) help math.sqrt # 関数ヘルプ
# 実行中に変数値変更:
# (Pdb) numeric_data.append(100) # リストに要素追加
# (Pdb) results['debug'] = True # 辞書に要素追加
test_data = [1, 2, 'three', 4.5, None, 6, 'seven', 8.9]
スタックフレーム操作とデバッグ
def outer_function(x):
print(f"Outer function called with {x}")
result = middle_function(x * 2)
return result
def middle_function(y):
print(f"Middle function called with {y}")
if y > 10:
return inner_function(y - 5)
else:
return y
def inner_function(z):
print(f"Inner function called with {z}")
breakpoint() # 最も深い関数でデバッグ開始
return z * z
# スタックフレーム操作コマンド:
# (Pdb) where / w # 完全なスタックトレース表示
# (Pdb) up / u # 上位フレーム(呼び出し元)に移動
# (Pdb) down / d # 下位フレーム(呼び出し先)に移動
# (Pdb) frame 0 # 特定フレーム番号に移動
# (Pdb) args # 現在のフレームの引数表示
# (Pdb) locals() # ローカル変数一覧
# (Pdb) globals() # グローバル変数一覧
result = outer_function(3)
エラーハンドリングとpost-mortem デバッグ
def risky_operation(data):
"""エラーが発生する可能性のある処理"""
results = []
for i, item in enumerate(data):
try:
if isinstance(item, str):
# 文字列を数値に変換
number = float(item)
elif isinstance(item, (int, float)):
number = item
else:
raise TypeError(f"Unsupported type: {type(item)}")
# 0による除算のリスク
result = 100 / number
results.append(result)
except (ValueError, TypeError) as e:
print(f"Error processing item {i}: {e}")
# エラー時にデバッガー起動
breakpoint()
continue
except ZeroDivisionError:
print(f"Division by zero for item {i}")
breakpoint()
continue
return results
# post-mortem デバッグの使用例
def main_with_error_handling():
test_data = ['10.5', 20, '0', 'invalid', 5.5, None]
try:
results = risky_operation(test_data)
print(f"Results: {results}")
except Exception:
# 未処理例外発生時にpost-mortem デバッグ
import pdb
pdb.post_mortem()
# post-mortem デバッグで可能な操作:
# (Pdb) l # エラー発生箇所のコード確認
# (Pdb) p item # エラー発生時の変数値
# (Pdb) p i # ループカウンター
# (Pdb) pp data # 全データの確認
# (Pdb) type(item) # エラーを起こした要素の型
リモートデバッグ設定
# リモートデバッグ用のサーバープログラム
import pdb
import socket
import sys
def remote_debugger_setup(host='localhost', port=4444):
"""リモートデバッグ用のpdb設定"""
class RemotePdb(pdb.Pdb):
def __init__(self, host, port):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.bind((host, port))
self.socket.listen(1)
print(f"Waiting for debugger connection on {host}:{port}")
client_socket, address = self.socket.accept()
print(f"Debugger connected from {address}")
# ソケットをファイルオブジェクトとして使用
self.handle = client_socket.makefile('rw')
pdb.Pdb.__init__(self, stdin=self.handle, stdout=self.handle)
return RemotePdb(host, port)
def server_function_with_debug():
data = [1, 2, 3, 4, 5]
# リモートデバッガー起動
debugger = remote_debugger_setup()
debugger.set_trace()
# 実際の処理
processed = [x * 2 for x in data]
return processed
# クライアント側からの接続:
# telnet localhost 4444
pdb++とIPDBの使用例
# pdb++のインストールと使用
pip install pdbpp
# IPDBのインストールと使用
pip install ipdb
# pdb++の場合(自動的にpdbを置き換え)
def enhanced_debug_example():
data = {'a': 1, 'b': [2, 3, 4], 'c': {'nested': True}}
breakpoint() # pdb++が起動(構文ハイライト、補完機能)
result = process_data(data)
return result
# IPDBの場合
def ipdb_example():
import ipdb
numbers = list(range(10))
ipdb.set_trace() # IPython統合デバッガー起動
# IPythonの豊富な機能が利用可能
# %timeit, %who, %whos など
squared = [n**2 for n in numbers]
return squared
カスタムpdbコマンド作成
import pdb
class CustomPdb(pdb.Pdb):
def do_list_vars(self, arg):
"""カスタムコマンド: 型別に変数一覧表示"""
frame = self.curframe
vars_by_type = {}
for name, value in frame.f_locals.items():
type_name = type(value).__name__
if type_name not in vars_by_type:
vars_by_type[type_name] = []
vars_by_type[type_name].append((name, value))
for type_name, vars_list in vars_by_type.items():
print(f"\n{type_name}:")
for name, value in vars_list:
print(f" {name} = {repr(value)}")
def do_memory_usage(self, arg):
"""カスタムコマンド: メモリ使用量表示"""
import sys
frame = self.curframe
print("Variable memory usage:")
for name, value in frame.f_locals.items():
size = sys.getsizeof(value)
print(f" {name}: {size} bytes")
# カスタムデバッガーの使用
def debug_with_custom_commands():
numbers = list(range(1000))
text = "Hello, World!" * 100
data_dict = {f"key_{i}": i for i in range(50)}
# カスタムpdbインスタンス作成
debugger = CustomPdb()
debugger.set_trace()
# カスタムコマンドが利用可能:
# (CustomPdb) list_vars # 型別変数一覧
# (CustomPdb) memory_usage # メモリ使用量
result = len(numbers) + len(text) + len(data_dict)
return result