开这个坑的原因是一开始想写一个基于manim的可视化排序程序,结果在这之中认识了decorator这个强大的功能,上头了。经过几周时间,我把这个程序用装饰器全部重写了一遍,对于交换类排序,可以只添加装饰器就直接生成动画。实现原理是”偷梁换柱”,装饰器将传入的list替换为自己写的UserList,从而达到监视list中变量变化情况的效果。但是美中不足的是这个程序只能实现简单的交换类排序的动画,一旦排序过程中开辟了新的空间,我是没有办法获取的。经过摸索,我找到了最有可能实现这个功能的方式:自己实现调试器!灵感来源于著名的开源项目PySnooper,这种使用装饰器来进行Debug的方式正对我胃口。于是,我开始阅读pysnooper的源码,自然而然地,我遇到了一切调试器的万恶之源——sys.settrace()。网上的中英文资料实在太少,为了弄清楚这其中的工作原理,在鸽了两个月没更博客后,在最后一科数据结构与算法考试的前夕,写下这段开头。

在学习trace之前,我们需要弄明白一些前置的概念:

装饰器swapper

上下文管理器与with语句

trace机制

学习完前两个部分后,我们终于正式开始认识trace机制了!

sys.settrace()

这是python内置的跟踪函数,用法如下:

1
2
3
4
5
6
sys.settrace(tracefun)
# 开启跟踪函数,之后的语句都会跟踪
sys.settrace(None)

# 其中,tracefunn的函数签名为:
def my_tracer(frame, event, arg = None)

这里的frame参数是当前的堆栈帧,event是当前帧的类型,arg目前不知道

首先来看最重要的frame,我们用以下代码来查看frame对象里都有啥:

1
2
3
4
5
6
7
callable(frame)	# False 不可调用

class emptyCls: pass # 创建空类
print( set(dir(frame)) - set(dir(emptyCls)) ) # 查看对象的属性
'''
{'f_trace_opcodes', 'clear', 'f_builtins', 'f_lineno', 'f_trace', 'f_trace_lines', 'f_code', 'f_lasti', 'f_globals', 'f_locals', 'f_back'}
'''

其中只读属性:

属性 说明
f_back
f_code
f_locals
f_globals
f_builtins
f_lasti