用sys._getframe()检查一次函数调用的参数绑定、局部变量和帧对象,验证*args/**kwargs是怎么打包的,f_back又是怎么串起调用链的。
一个带默认值、*args、**kwargs的函数,被调用时这些参数怎么归位。
代码def outer(a, b=10, *args, **kwargs): frame = sys._getframe() print(frame.f_locals) inner() def inner(): frame = sys._getframe() print(frame.f_back.f_code.co_name) outer(1, 2, 3, 4, k=5)
调用时传的是1,2,3,4,k=5这5个值,看落到局部变量里是什么样子。
结果f_locals: {'a': 1, 'b': 2, 'args': (3, 4), 'kwargs': {'k': 5}, 'frame': <frame object>} code.co_varnames: ('a', 'b', 'args', 'kwargs', 'frame') code.co_argcount: 2
验证:co_argcount=2 只统计a和b这两个的位置参数,*args和**kwargs不算在内;调用时多出来的3和4被打包进了一个元组赋给args,多出来的k=5被打包进了一个字典赋给kwargs——这个打包过程发生在函数调用那一刻,不是延迟到你在函数体里第一次用到args的时候才做的。
inner()是被outer()调用的,inner内部怎么知道是谁调用了自己。
结果inner f_back.f_code.co_name: outer inner f_back.f_locals keys: ['a', 'b', 'args', 'kwargs', 'frame']
在inner()函数体内,sys._getframe().f_back 就是outer()那一个帧对象——不是同名同参数的另一个新对象,是同一个对象,能直接读到outer里此刻的局部变量值(包括args、kwargs)。这也是traceback模块打印异常调用链、以及pdb这类调试器能"跳到上一层看变量"的底层机制:每个帧对象都用f_back指向调用它的那一帧,串成一条链,一路能走到最外层的模块级帧(此时f_back为None)。
f_locals 每次被访问时才从这块数组同步生成一个字典返回,直接修改这个字典里的值,通常不会反过来影响函数体里下一次读取该变量时看到的结果——这也是为什么pdb里"改一个局部变量"有时候不生效,根源就在这里。
每一步都是本文上面用sys._getframe()跑出来的原始结果。
sys._getframe() / frame.f_locals / frame.f_back:检查参数绑定与调用链