如何在Pyodide中实现Python代码的分步调试执行?
在Pyodide中实现分步调试(单步执行/断点/变量检查)
分步执行完全可行,核心思路是借助Python内置的调试跟踪机制(sys.settrace 或 pdb 模块),结合Pyodide的JS-Python交互能力,将调试控制权交还给前端UI,实现单步、断点、变量检查等功能。以下是具体实现方案:
方案一:自定义跟踪函数(灵活度高,适合教学场景)
通过sys.settrace注册自定义跟踪函数,捕获代码执行的行事件、函数调用/返回事件,在需要暂停的节点(断点、单步指令)将状态同步到前端,等待用户操作后继续执行。
1. Python端核心代码
import sys import asyncio from pyodide import to_js # 调试状态存储,供JS端修改控制 debug_state = { "breakpoints": set(), # 存储断点行号 "current_action": "continue", # 控制指令:continue/step_in/step_over/step_out "is_paused": False, "current_frame": None } def trace_handler(frame, event, arg): global debug_state if event == "line": line_no = frame.f_lineno # 触发暂停条件:到达断点 或 当前指令为单步 if line_no in debug_state["breakpoints"] or debug_state["current_action"] in ["step_in", "step_over"]: debug_state["current_frame"] = frame # 将当前调试状态同步到前端 js.debugger.onPause(to_js({ "type": "line", "line": line_no, "file": frame.f_code.co_filename, "locals": to_js(frame.f_locals), "globals": to_js(frame.f_globals) })) # 等待前端指令 while debug_state["is_paused"]: asyncio.sleep(0.01) # 让出Pyodide事件循环控制权 # 单步指令执行后重置 if debug_state["current_action"] in ["step_in", "step_over"]: debug_state["current_action"] = "continue" elif event == "call": # 单步进入函数时暂停 if debug_state["current_action"] == "step_in": debug_state["current_frame"] = frame js.debugger.onPause(to_js({ "type": "call", "function": frame.f_code.co_name, "locals": to_js(frame.f_locals) })) while debug_state["is_paused"]: asyncio.sleep(0.01) elif event == "return": # 单步跳出函数时暂停 if debug_state["current_action"] == "step_out": debug_state["current_frame"] = frame js.debugger.onPause(to_js({ "type": "return", "function": frame.f_code.co_name, "return_value": arg })) while debug_state["is_paused"]: asyncio.sleep(0.01) debug_state["current_action"] = "continue" return trace_handler # 注册跟踪函数 sys.settrace(trace_handler)
2. JS端控制逻辑
通过Pyodide全局对象修改Python端的debug_state,响应用户操作(单步、继续、添加断点):
// 前端调试器控制对象 window.debugger = { // 接收Python端同步的暂停状态 onPause: function(state) { // 在这里更新UI:显示当前行号、变量列表等 console.log(`暂停于行 ${state.line}`); console.log("局部变量:", state.locals); // 绑定按钮事件(示例) document.getElementById("step-over-btn").onclick = () => { pyodide.globals.get("debug_state").set("current_action", "step_over"); pyodide.globals.get("debug_state").set("is_paused", false); }; document.getElementById("continue-btn").onclick = () => { pyodide.globals.get("debug_state").set("current_action", "continue"); pyodide.globals.get("debug_state").set("is_paused", false); }; document.getElementById("step-in-btn").onclick = () => { pyodide.globals.get("debug_state").set("current_action", "step_in"); pyodide.globals.get("debug_state").set("is_paused", false); }; document.getElementById("step-out-btn").onclick = () => { pyodide.globals.get("debug_state").set("current_action", "step_out"); pyodide.globals.get("debug_state").set("is_paused", false); }; // 设置暂停标记,等待用户操作 pyodide.globals.get("debug_state").set("is_paused", true); }, // 添加断点 addBreakpoint: function(lineNo) { pyodide.globals.get("debug_state").get("breakpoints").add(lineNo); } }; // 异步运行目标脚本(必须包装为异步函数,避免阻塞事件循环) async function runDebugScript(script) { await pyodide.runAsync(` import asyncio async def execute_script(): ${script} asyncio.run(execute_script()) `); }
方案二:基于pdb模块(复用Python原生调试能力)
如果想直接复用Python的pdb调试命令(如n单步跳过、s单步进入),可以重定向sys.stdin/stdout,将pdb的输入输出与前端UI绑定。
核心代码示例
import sys from pyodide.ffi import create_proxy # 自定义IO类,对接前端 class PDBIO: def readline(self): # 等待前端输入调试命令 return js.debugger.getPDBCommand() def write(self, data): # 将pdb输出发送到前端 js.debugger.showPDBOutput(data) # 替换标准IO sys.stdin = PDBIO() sys.stdout = PDBIO() # 启动pdb调试 import pdb target_script = """ def add(a, b): return a + b x = 10 y = 20 z = add(x, y) print(z) """ pdb.run(target_script)
JS端实现getPDBCommand和showPDBOutput,比如通过输入框获取用户命令,通过控制台或UI区域显示输出。
关键注意事项
- 异步执行:必须将目标脚本包装为异步函数并通过
pyodide.runAsync执行,否则跟踪函数中的asyncio.sleep会阻塞Pyodide事件循环。 - 变量序列化:使用
pyodide.to_js转换Python变量时,复杂对象(如自定义类)需要手动处理__dict__属性来实现序列化。 - 性能优化:逐行跟踪会降低代码执行速度,教学场景下可接受,但需避免在大规模脚本中使用。
- 作用域处理:跟踪函数中的
frame.f_locals和frame.f_globals分别对应当前执行的局部和全局作用域,可用于展示变量。
内容的提问来源于stack exchange,提问作者tglas
相关产品推荐
相关产品推荐

