bigywater.tech
Python · 22

Python 函数调用:参数绑定、局部变量和栈帧

用sys._getframe()检查一次函数调用的参数绑定、局部变量和帧对象,验证*args/**kwargs是怎么打包的,f_back又是怎么串起调用链的。

co_argcount=2
只统计位置参数,不含*args/**kwargs
(3,4)/{'k':5}
*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)

验证:f_locals把*args/**kwargs打包成了两个具体对象

调用时传的是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的时候才做的。

验证:f_back串起了调用链,不需要额外的调用栈参数

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)。

frame.f_locals是一份"快照"而不是实时视图:CPython的正常函数(非生成器)内部用一块连续的局部变量数组存储变量,f_locals 每次被访问时才从这块数组同步生成一个字典返回,直接修改这个字典里的值,通常不会反过来影响函数体里下一次读取该变量时看到的结果——这也是为什么pdb里"改一个局部变量"有时候不生效,根源就在这里。

分步演示:栈帧记录了什么

每一步都是本文上面用sys._getframe()跑出来的原始结果。

一次函数调用的参数打包与帧链
这一步新验证的结果

    本文用到的工具

    • 本机 Python 3.14.7
    • sys._getframe() / frame.f_locals / frame.f_back:检查参数绑定与调用链
    京ICP备2026067502号-1京公网安备11010502063487号