必须用args和*kwargs的场景是调用方不可控时,如写装饰器、封装第三方接口、通用回调钩子;二者需按固定顺序使用,且需注意类型校验、键名冲突、可读性与测试覆盖。

什么时候必须用 *args 和 **kwargs
当函数签名不确定参数数量或类型时,比如写装饰器、封装第三方接口、做通用回调钩子——这时候硬写死参数名会立刻报 TypeError: unexpected keyword argument。不是“想灵活”,而是调用方根本不受你控制。
常见场景:functools.wraps 装饰器里必须透传原函数的所有参数;__init__ 中转发父类初始化参数;日志埋点函数需要捕获任意调用上下文。
- 只用
*args:处理位置参数变长,比如sum_all(*numbers),但无法接收关键字参数 - 只用
**kwargs:能收关键字参数,但位置参数多了会直接报错TypeError: takes 0 positional arguments but X were given - 两者一起用:顺序固定为
def f(a, b, *args, c=1, **kwargs),中间的*args会吃掉多余位置参数,**kwargs吃掉多余关键字参数
*args 不是万能的“参数兜底”
它把所有多余位置参数打包成一个 tuple,但不会帮你解包、校验或转换类型。很多人以为加了 *args 就能“兼容一切”,结果在函数体里直接对 args 做 args[0].upper(),却忘了 args 可能是空元组,或者第一个参数根本不是字符串。
- 别假设
args非空:先检查if args:再取args[0] - 别直接对
args做类型操作:要遍历就得for arg in args:,而不是args.upper() - 和默认参数混用时注意覆盖逻辑:例如
def f(x=10, *args),调用f(5)会让x=5,args=();但f()才让x=10
**kwargs 的键名冲突与静默覆盖
如果函数已有明确参数名(比如 def send_email(to, subject, **kwargs)),而调用方传了 send_email("a@b.c", "hi", to="x@y.z"),就会触发 TypeError: send_email() got multiple values for argument 'to'——因为 to 既被位置参数占了,又被 **kwargs 重复提供。
立即学习“Python免费学习笔记(深入)”;
- 避免在已有参数名和
**kwargs之间形成重名:要么改参数名(如用to_addr),要么文档注明“不要传已定义的 key” -
**kwargs里的键必须是字符串,否则报TypeError: keywords must be strings;传{1: "a"}会崩,不是警告 - 传入的字典如果含非法标识符(如
{"user-id": "abc"}),key 里带短横线,后续没法用kwargs.user-id访问,只能用kwargs["user-id"]
性能和可读性的真实代价
加 *args 和 **kwargs 几乎不增加运行时开销,但会让 IDE 类型推导失效、静态检查变弱、调用栈更难读。PyCharm 看不到参数提示,mypy 默认跳过这类函数的参数校验,出错时堆栈里只显示 func(*args, **kwargs),看不出实际传了啥。
- 团队协作中,优先用显式参数 +
Optional或Union类型注解,而不是一股脑塞**kwargs - 如果真要保留
**kwargs,至少在 docstring 里写清接受哪些 key,比如"""Supports: timeout (int), retry (bool), verify_ssl (bool)""" - 测试时别只测
func(1, 2, extra="x"),还得测func(1, 2)(空**kwargs)、func(1, 2, timeout=0)(边界值)、func(1, 2, unknown_key="y")(是否静默忽略)
最常被忽略的是:你以为传进 **kwargs 的东西,在下游函数里可能已经被当作“标准参数”处理了——比如你透传 **kwargs 给 requests.get(),但没意识到 requests 自己也支持 **kwargs,两层展开后 key 冲突或语义错位,问题就藏得特别深。










