有人询问Python调试器的设计原理,这里简单说明一下。
Python本身带了一个pdb调试库,但并没有调试界面。
我之前曾经写过一个pdb的派生类,用于在Script.NET集成开发环境中集成python调试器,代码如下。整体思路是:
pdb类中已经定义了几个用于派生的成员函数,例如user_call是调用一个函数时候会被调用的函数,user_line是执行每一行之前都会被调
用的函数,user_return是函数返回时候会被调用的函数,user_exception是发生异常时候会被调用的函数。
只要重载上面需要的函数做自己的处理就可以,例如重载user_line函数,在此函数中判断是否在一个断点上,或者是否是单步调试状态,如果需要停下
来,就通过一个消息循环使当前运行的脚本停下来等候用户输入,同时在某个界面窗口中显示变量等信息。
#################################################################
# Script.NET Python support script
# Copyright Blueant Software.
# Author : blueant
# Create Date : 2002-06-11
# This script is platform python debugger support library.
#################################################################
"""Platform python debugger support library.
"""
import sys
import bdb
import _platform
from types import *
from TclInterp import *
# get sting format value of variable
def stringify(thing):
try:
typ = type(thing)
if hasattr(thing, '__name__'):
return thing.__name__
#if hasattr(typ, '__name__'):
# return thing.__name__ + ' (' + typ.__name__ + ')'
#else:
# return thing.__name__ + ' (' + str(typ) + ')'
else:
return repr(thing)
except:
return "Looking at object failed - %s: %s" % (sys.exc_info()
[0], sys.exc_info()[1])
def ismultitem(thing):
typ = type(thing)
return 0
# if (typ == ListType || typ == TupleType || typ == DictType):
# return 1
# elif (typ == ClassType || typ == ModuleType || typ ==
InstanceType):
# return 2
# else:
# return 0
class Xdb(bdb.Bdb):
def __init__(self):
bdb.Bdb.__init__(self)
self.reset()
self.forget()
self.curframe = None
self.init_vars_window()
def forget(self):
self.lineno = None
self.stack = []
self.curindex = 0
self.curframe = None
def setup(self, f, t):
self.forget()
self.stack, self.curindex = self.get_stack(f, t)
self.curframe = self.stack[self.curindex][0]
def user_call(self, frame, args):
return
#if not self.stop_here(frame): return
#name = frame.f_code.co_name
#if not name: name = '???'
#_platform.pydbg(_platform.interpid, 0,
frame.f_code.co_filename, name, frame.f_lineno, '')
def user_line(self, frame):
if frame.f_lineno == 0:
return
self.setup(frame, None)
#self.refresh_debuginfo()
name = frame.f_code.co_name
if not name: name = ''
_platform.pydbg(_platform.interpid, 1,
frame.f_code.co_filename, name, frame.f_lineno, '')
self.forget()
def user_return(self, frame, retval):
return
#_platform.pydbg(_platform.interpid, 2,
frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno, '')
def user_exception(self, frame, exc_stuff):
#_platform.pydbg(_platform.interpid, 3,
frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno+1,
exc_stuff)
print '+++ exception', exc_stuff
self.set_continue()
# set/clear breakpoint
def bp_set(self, filename, lineno, set):
if set :
self.set_break(filename, lineno)
else:
self.clear_break(filename, lineno)
def init_vars_window(self):
# local vars
self.window_locals = xpOutputWindow('局部', 0, 2, 1)
self.window_locals.ModifyStyle('', 'SINGLESELECT')
self.window_locals.SetFont('FixedSys')
self.window_locals.DeleteRow(-1)
self.window_locals.DelColumn(-1)
self.window_locals.AddColumn('变量名', 120, 'left')
self.window_locals.AddColumn('值', 120, 'left')
self.window_locals.AddColumn('类型', 80, 'left')
#self.window_locals.ReadProfile('OwmEdit//Debug', 'VarWatch')
# global vars
self.window_globals = xpOutputWindow('全局', 0, 2, 1)
self.window_globals.ModifyStyle('', 'SINGLESELECT')
self.window_globals.SetFont('FixedSys')
self.window_globals.DeleteRow(-1)
self.window_globals.DelColumn(-1)
self.window_globals.AddColumn('变量名', 120, 'left')
self.window_globals.AddColumn('值', 120, 'left')
self.window_globals.AddColumn('类型', 80, 'left')
#self.window_globals.ReadProfile('OwmEdit//Debug', 'VarWatch')
def init_stack_window(self):
self.window_stack = xpOutputWindow('堆栈', 0, 1, 1)
self.window_stack.ModifyStyle('', 'SINGLESELECT')
self.window_stack.SetRowColors('0,0,255', '255,255,225')
self.window_stack.DeleteRow(-1)
self.window_stack.DelColumn(-1)
self.window_stack.AddColumn('函数', 120, 'left')
self.window_stack.AddColumn('行', 50, 'left')
self.window_stack.AddColumn('文件', 150, 'left')
self.window_stack.AutoSizeColumn(2)
# initial var windows' double click script
def init_vars_window_script(self):
# local var window
script = """
set item [output -GetInfo "局部" SelItem];
set name [output -GetInfo "局部" <$item,0>];
set value [output -GetInfo "局部" <$item,1>];
set type [output -GetInfo "局部" <$item,2>];
MessageBox -MBOK -MB_ICONINFORMATION -title "查看变量" /
"变量名:$name /n变量值:$value /n变量类型:$type";
unset item name value type;
""";
self.window_locals.AppendRowScript(script);
# global var window
script = """
set item [output -GetInfo "全局" SelItem];
set name [output -GetInfo "全局" <$item,0>];
set value [output -GetInfo "全局" <$item,1>];
set type [output -GetInfo "全局" <$item,2>];
MessageBox -MBOK -MB_ICONINFORMATION -title "查看变量" /
"变量名:$name /n变量值:$value /n变量类型:$type";
unset item name value type;
""";
self.window_globals.AppendRowScript(script);
# refresh debug info
def refresh_debuginfo(self):
#self.init_stack_window()
#self.init_vars_window()
self.refresh_stack()
self.refresh_vars(self.curframe)
def refresh_vars(self, frame):
self.init_vars_window()
self.window_locals.DeleteRow(-1)
self.window_globals.DeleteRow(-1)
self.init_vars_window_script()
# local vars
row = 0
for key in frame.f_locals:
if not (key[:2] == key[-2:] == '__'):
self.window_locals.AppendRow(key)
self.window_locals.SetItemText(row, 1,
stringify(frame.f_locals[key]))
if ismultitem(frame.f_locals[key]):
self.window_locals.SetTextColor(row, 1, 5)
self.window_locals.SetItemText(row, 2,
type(frame.f_locals[key]).__name__)
self.window_locals.SetRowScript(row, 1);
row += 1
# global vars
row = 0
for key in frame.f_globals:
if not (key[:2] == key[-2:] == '__'):
self.window_globals.AppendRow(key)
self.window_globals.SetItemText(row, 1,
stringify(frame.f_globals[key]))
if ismultitem(frame.f_globals[key]):
self.window_globals.SetTextColor(row, 1, 5)
self.window_globals.SetItemText(row, 2,
type(frame.f_globals[key]).__name__)
self.window_globals.SetRowScript(row, 1);
row += 1
def refresh_stack(self):
self.init_stack_window()
self.window_stack.DeleteRow(-1)
row = 0
for f, lineno in self.stack:
self.window_stack.AppendRow(f.f_code.co_name, 21)
self.window_stack.SetItemText(row, 1, lineno)
self.window_stack.SetItemText(row, 2,
f.f_code.co_filename)
row += 1
self.window_stack.AutoSizeColumn(2)
if __name__=='__main__':
x = Xdb()
filename = 'e://work//platform//samples//python//TestTclCmd.py'
x.set_break(filename, 15)
print x.run('execfile(' + `filename` + ')')