args和kwargs是CPython调用栈层面的解包机制:调用时args打包剩余位置参数为tuple,kwargs打包未匹配关键字参数为dict;定义时则反向解包;二者顺序固定且不可颠倒,是装饰器通用性的基础。

Python 函数调用时,*args 和 **kwargs 实际发生了什么
它们不是语法糖,而是解释器级的参数解包机制:调用函数时,*args 把位置参数打包成 tuple,**kwargs 把关键字参数打包成 dict;定义函数时,它们则反向做解包。关键在于——这个过程发生在 CPython 的调用栈层面,不经过 Python 层的普通对象构造逻辑。
实操建议:
-
*args接收的是「所有未被显式形参匹配的剩余位置参数」,不是「所有传入的位置参数」——如果前面有必填参数没填,会直接报TypeError: missing 1 required positional argument,根本轮不到*args出场 -
**kwargs只接收「未被显式命名形参捕获的关键字参数」,且要求所有键名必须是str;传入**{1: 'a'}会触发TypeError: keywords must be strings - 两者顺序不能颠倒:定义中必须是
(*args, **kwargs),写成(**kwargs, *args)是语法错误
为什么 def f(*args, x=1) 会报错,但 def f(*, x=1) 可以
因为 *args 是“贪婪”的位置参数收集器,它后面不能再跟普通位置形参(如 x=1),否则解释器无法判断哪个参数该进 *args、哪个该赋给 x。而 * 单独出现,是「仅限关键字参数」的标记,和 *args 无关,它只规定其后的参数必须用关键字传入。
常见错误现象:
立即学习“Python免费学习笔记(深入)”;
-
def f(*args, x=1): pass→SyntaxError: invalid syntax -
def f(*, x=1): pass→ 合法,调用必须写成f(x=2),不能写f(2) - 想同时支持收集位置参数 + 强制关键字参数?只能写成
def f(*args, x=1, **kwargs): pass,此时x是可选关键字参数,不是仅限关键字参数
*args 和 **kwargs 在装饰器里为什么几乎必用
装饰器本质是函数替换,新函数必须能接收原函数的所有调用方式,否则一调就崩。不用 *args 和 **kwargs,你就得为每个被装饰函数单独写适配签名,完全失去通用性。
性能与兼容性注意点:
- 它们本身几乎没有运行时开销,但过度嵌套装饰器(比如 5 层都带
*args, **kwargs)会让调用栈变深,对调试和inspect.signature()解析有影响 - 某些静态分析工具(如 mypy)对
*args, **kwargs推导类型能力有限,若需类型安全,应配合typing.ParamSpec(Python 3.10+)或显式泛型 - 不要在装饰器里修改
args或kwargs后直接透传,除非你清楚原始函数是否依赖参数的可变性(比如传了list被原地修改)
传参时 *[1,2] 和 **{'x':1} 的实际行为
这是调用端的解包,和函数定义里的 *args / **kwargs 是镜像关系:解释器把容器内容“铺开”成独立参数,再按位置/关键字规则匹配目标函数签名。
容易踩的坑:
-
*[1,2]等价于写1, 2,不是传一个列表;若目标函数只接受 1 个参数,就会报TypeError: f() takes 1 positional argument but 2 were given -
**{'x':1, 'y':2}要求字典 key 必须全为字符串,且不能和已有关键字参数重名,否则报TypeError: f() got multiple values for argument 'x' -
*None或**None会立刻触发TypeError: argument after * must be an iterable或argument after ** must be a mapping,不会等到函数内部才检查
函数签名和参数传递是解释器在调用瞬间完成的两套并行机制,*args 和 *kwargs 是它们之间唯一的、不可绕过的桥接点。漏掉其中一个环节的约束(比如以为 args 能接住所有参数,却忘了前面还有必填形参),就会在最意想不到的地方崩。










