我正在编写一个小的python IDE,我想添加简单的调试。我不需要winpdb的所有功能。 如何启动一个python程序(按文件名),并在行号处设置断点,以便它运行到该行号并停止? 请注意,我不想从命令行执行此操作,并且我不想编辑源(例如,通过插入set_trace)。我不希望它停在第一行,所以我必须从那里运行调试器。我已经尝试了pdb和bdb的所有显而易见的方法,但我必须遗漏一些东西。
答案 0 :(得分:7)
实际上唯一可行的方法(就我所知)是从IDE中运行Python作为子进程。这避免了当前Python解释器的“污染”,这使得程序很可能以与独立启动它相同的方式运行。 (如果您遇到此问题,请检查子进程环境。)以这种方式,您可以使用
在“调试模式”下运行脚本p = subprocess.Popen(args=[sys.executable, '-m', 'pdb', 'scriptname.py', 'arg1'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
这将在调试器提示符下启动Python。您需要运行一些调试器命令来设置断点,您可以这样做:
o,e = p.communicate('break scriptname.py:lineno')
如果这样做,o
应该是设置断点后Python解释器的正常输出,e
应为空。我建议您使用它并在代码中添加一些检查以确保断点是否正确设置。
之后,您可以使用
启动运行的程序p.communicate('continue')
此时,您可能希望将输入,输出和错误流挂接到您嵌入IDE的控制台。您可能需要使用事件循环来执行此操作,大致如下:
while p.returncode is None:
o,e = p.communicate(console.read())
console.write(o)
console.write(e)
您应该认为该代码段是有效的伪代码,因为根据您的控制台的工作原理,它可能需要一些修补才能使其正确。
如果这看起来过于混乱,您可以使用Python的pdb
和bdb
模块(我分别猜测“Python调试器”和基本调试器)的功能来简化过程。关于如何执行此操作的最佳参考是pdb
模块本身的源代码。基本上,模块的职责分离的方式是bdb
处理“引擎盖下”调试器功能,如设置断点,或者停止并重新启动执行; pdb
是一个包装器来处理用户交互,即读取命令和显示输出。
对于集成IDE的调试器,以我能想到的两种方式调整pdb
模块的行为是有意义的:
通过子类化pdb.Pdb
,这两个更改应该很容易实现。您可以创建一个子类,其初始化程序将断点列表作为附加参数:
class MyPDB(pdb.Pdb):
def __init__(self, breakpoints, completekey='tab',
stdin=None, stdout=None, skip=None):
pdb.Pdb.__init__(self, completekey, stdin, stdout, skip)
self._breakpoints = breakpoints
实际设置断点的逻辑位置就在调试器读取.pdbrc
文件之后,该文件发生在pdb.Pdb.setup
方法中。要执行实际设置,请使用从set_break
继承的bdb.Bdb
方法:
def setInitialBreakpoints(self):
_breakpoints = self._breakpoints
self._breakpoints = None # to avoid setting breaks twice
for bp in _breakpoints:
self.set_break(filename=bp.filename, line=bp.line,
temporary=bp.temporary, conditional=bp.conditional,
funcname=bp.funcname)
def setup(self, f, t):
pdb.Pdb.setup(self, f, t)
self.setInitialBreakpoints()
这段代码适用于作为例如传递的每个断点。一个名为元组的元组。您也可以尝试直接构建bdb.Breakpoint
实例,但我不确定这是否可以正常工作,因为bdb.Bdb
维护自己的断点信息。
接下来,您需要为模块创建一个新的main
方法,该方法以pdb
运行的方式运行它。在某种程度上,您可以从main
(当然还有pdb
语句)复制if __name__ == '__main__'
方法,但是您需要通过某种方式来增加它以传递有关的信息你的额外断点。我建议将断点写入IDE中的临时文件,并将该文件的名称作为第二个参数传递:
tmpfilename = ...
# write breakpoint info
p = subprocess.Popen(args=[sys.executable, '-m', 'mypdb', tmpfilename, ...], ...)
# delete the temporary file
然后在mypdb.main()
中,你会添加如下内容:
def main():
# code excerpted from pdb.main()
...
del sys.argv[0]
# add this
bpfilename = sys.argv[0]
with open(bpfilename) as f:
# read breakpoint info
breakpoints = ...
del sys.argv[0]
# back to excerpt from pdb.main()
sys.path[0] = os.path.dirname(mainpyfile)
pdb = Pdb(breakpoints) # modified
现在您可以像使用pdb
一样使用新的调试器模块,除了您不必在进程启动之前显式发送break
命令。这样做的好处是,如果允许你这样做,你可以直接将Python子进程的标准输入和输出挂钩到你的控制台。