如何编写Python调试器/编辑器

3 投票
3 回答
2683 浏览
提问于 2025-04-16 01:10

抱歉提了个比较笼统的问题。关于我想要的内容,具体是这样的:

我希望用户能够编写一些Python代码并执行。当出现一个没有被处理的错误时,我希望调试器能够暂停执行,显示当前的状态、环境、调用栈和错误信息,并且能够让用户编辑代码

我只想让出错的特定代码块可以编辑,其他地方不动(暂时如此)。也就是说,如果错误发生在一个for循环里,我只想让for循环内部的代码块可以编辑。应该是用户编辑器范围内最新的代码块(而不是其他库或Python自带的库里的代码)。在这种情况下,编辑哪个代码块就会很清楚。


我已经尝试过研究一下怎么实现这个功能,但感觉有点迷茫。

Python的错误追踪信息并没有直接给我出错的代码块,只告诉我出错的函数和代码行。我可以反推回去,但这让我觉得有点不太靠谱。更好(也更自然)的是,如果我能以某种方式获取到代码的抽象语法树(AST)中的代码块。

为了获取AST(并对其进行操作,比如修改),我可能会使用compiler(这个好像被弃用了?)和/或parser模块,或者ast模块。不过我不太确定如何在AST中重新编译特定的节点/代码块,或者我是否只能重新编译整个函数。

3 个回答

1

根据我目前的理解,这个事情是不可能的。至少不能按代码块来处理。可以按函数重新编译代码,但不能按代码块,因为Python是按函数来生成代码对象的。

所以唯一的办法就是自己写一个Python编译器。

1

我在想,HAP远程调试器对于Python可能对你有帮助吗?我觉得它可能没有实时编辑的功能,但它的一些调试功能可能还是挺有用的。

2

在玩弄 astcompile 这两个内置功能时,似乎可以用 NodeTransformer 来修改一些节点……如果你知道自己在找什么,也可以手动编辑它们。

test.py

print 'Dumb Guy'
x = 4 + 4
print x * 3

change.py

import ast
with open('test.py') as f:
    expr = f.read()

e = ast.parse(expr)
e.body[0].values[0].s = 'Cool Guy'       # Replace the string
e.body[1].targets[0].id = 'herring'      # Change x to herring
e.body[2].values[0].left.id = 'herring'  # Change reference to x to reference to herring
c = compile(e, '<string>', 'exec')
exec(c)

change.py 的输出结果:

酷哥
24

你也可以通过这种方式在代码主体中添加代码(或者像平常那样替换列表中的元素):

p = ast.parse('print "Sweet!"', mode='single')
e.body.extend(p)

然后只需重新编译并执行:

c = compile(e, '<string>', 'exec')
exec(c)

你可以用这种方式替换函数定义或单行代码。函数定义会有自己的主体,所以如果你添加了某个函数(或循环),你可以通过

e.body[N].body  # Replace N with the index of the FunctionDef object

来访问它。

不过,我知道的唯一执行单个 ast 对象(比如 _ast.Print_ast.Assign 等等)的方法是这样做:

e2 = ast.parse('', mode='exec')
e2.body.append(e.body[0])
exec(compile(e2, '<string>', 'exec'))

这对我来说有点像是变通的办法。至于代码行,每个 AST 对象都有一个 lineno 属性,所以如果你能从异常中获取行号,就可以很容易找出是哪个语句引发了异常。

当然,这并没有真正解决将堆栈回滚到异常发生前的状态的问题,这似乎是你真正想要做的。不过,通过 pdb 可能可以做到这一点。

撰写回答