CircuitPython如何实现类似python -i的崩溃后保留变量进REPL
CircuitPython崩溃/终止时保留上下文进入REPL的实操方案
CircuitPython默认的执行逻辑和桌面CPython有本质区别:主脚本(code.py,部分老版本固件下为main.py)是在独立的临时命名空间内执行的,无论脚本正常运行结束还是抛异常崩溃,解释器都会在流程完成后主动回收这个临时命名空间的全部内存,因此即使按提示按键进入REPL,也无法回溯到脚本运行时定义的变量、函数。
下面是几个和桌面端python -i调试效果等价的实现方法,按易用性排序:
方法1:零侵入手动交互加载脚本(适合临时调试)
不需要修改原有代码逻辑,利用CircuitPython REPL本身的全局上下文执行脚本即可实现等价的交互调试效果:
- 上电启动时,在崩溃提示弹出前按任意键跳过主脚本的自动执行,直接进入原生REPL
- 在REPL中执行以下命令加载你的业务脚本:
exec(open('code.py').read(), globals())
这种加载方式的执行上下文和REPL自身的全局域完全共用,脚本中定义的所有变量、函数都会直接存到REPL的可访问作用域中,哪怕脚本中途崩溃抛出异常,也不会触发临时命名空间回收,你可以直接在REPL中调用之前定义的变量、函数排查问题,效果和桌面端python -i code.py完全一致。
方法2:脚本内托管调试上下文(适合自动化触发调试)
如果希望脚本崩溃后自动保留上下文、不需要提前按键跳过启动,可以在业务代码外层套一层上下文托管逻辑,把脚本的全局命名空间挂到常驻内存的内置模块上,避免被GC回收:
- 在主脚本的最外层全局作用域添加以下逻辑:
import builtins import traceback # 把当前脚本的全局命名空间挂到内置模块,避免崩溃后被回收 builtins.debug_context = globals() try: # ———————————————— # 原有全部业务代码都放在这个try块内 # ———————————————— sensor_reading = 4095 def calculate_avg(buf): return sum(buf) / len(buf) # 模拟触发崩溃的逻辑 1 / 0 # ———————————————— # 原有代码结束 # ———————————————— except Exception as err: # 打印完整崩溃栈 traceback.print_exception(err) print("\n调试入口:输入 debug_context 即可访问所有运行时变量/函数") # 主动触发中断进入REPL raise KeyboardInterrupt - 脚本崩溃进入REPL后,无需额外记忆变量名,直接访问
debug_context这个字典即可拿到所有运行时的变量、函数引用。如果觉得每次写字典访问麻烦,进REPL后可以先执行ctx = debug_context,后续直接通过ctx['sensor_reading']、ctx['calculate_avg']的方式调用,和在脚本内部访问的体验完全一致。
关键注意事项
- 这种把上下文挂到
builtins的方式会强制让GC保留相关变量的内存引用,调试完成后记得删除对应的托管代码,避免长期运行造成内存泄漏。 - 不要依赖CircuitPython默认的崩溃提示按键进入REPL获取上下文:这个提示弹出时,解释器已经完成了临时命名空间的内存回收,原变量引用已经被完全释放,无论怎么操作都无法从默认流程中拿到之前的上下文。
- 如果调试时需要快速定位问题,也可以在崩溃前手动把重点排查变量单独存到
builtins下,减少上下文遍历的工作量。
内容的提问来源于stack exchange,提问作者jadsq
相关产品推荐
相关产品推荐

