Python 中 *args、**kwargs 和参数顺序怎么理解?
简化版
*args 用来接收多余的位置参数,结果是元组;**kwargs 用来接收多余的关键字参数,结果是字典。Python 函数参数有固定顺序,理解位置参数、仅限关键字参数、默认参数和可变参数,才能写出清晰的函数签名。
详细版
常见写法:
def func(a, b=0, *args, c=1, **kwargs):
print(a, b, args, c, kwargs)
func(10, 20, 30, 40, c=5, debug=True)
# a=10, b=20, args=(30, 40), c=5, kwargs={'debug': True}
几个关键点:
*args收集额外位置参数,类型是tuple。**kwargs收集额外关键字参数,类型是dict。- 放在
*args后面的普通参数必须用关键字传递,称为仅限关键字参数。 *可以单独出现,用来声明后续参数只能按关键字传。/可以用来声明前面的参数只能按位置传。
例如:
def connect(host, port, *, timeout=3, retry=2):
...
connect("localhost", 3306, timeout=5)
这里 timeout 和 retry 必须写名字,调用时更清晰。
完整版教学
一、函数参数为什么要分类
函数签名不只是为了让代码能跑,它还表达 API 的使用方式。
例如:
def create_user(name, age, is_admin=False):
...
调用时如果写成:
create_user("Tom", 18, True)
第三个参数的含义不够直观。改成仅限关键字参数会更清楚:
def create_user(name, age, *, is_admin=False):
...
create_user("Tom", 18, is_admin=True)
这就是参数设计的价值:让调用者不容易写错。
二、*args 的本质
*args 会把多出来的位置参数收进一个元组:
def add(*args):
total = 0
for x in args:
total += x
return total
print(add(1, 2, 3)) # 6
这里的 args 只是习惯名字,不是语法关键字。下面也能运行:
def add(*numbers):
return sum(numbers)
但工程里推荐使用 args,因为它是约定俗成的可读写法。
三、**kwargs 的本质
**kwargs 会把多出来的关键字参数收进一个字典:
def log(message, **kwargs):
print(message, kwargs)
log("hello", user_id=1, trace_id="abc")
# hello {'user_id': 1, 'trace_id': 'abc'}
它常用于:
- 封装第三方库时透传配置;
- 写装饰器时保留原函数参数;
- 构造对象时接收可选字段。
但不要滥用 **kwargs。如果一个参数是稳定业务含义,应该显式写出来,让 IDE、类型检查和读代码的人都能看懂。
四、参数顺序规则
Python 的函数参数大体按下面顺序排列:
def func(pos_only, /, normal, default=0, *args, kw_only, **kwargs):
...
含义如下:
| 写法 | 含义 |
|---|---|
/ 前面的参数 | 只能按位置传 |
| 普通参数 | 可按位置或关键字传 |
| 有默认值参数 | 不传时使用默认值 |
*args | 收集额外位置参数 |
* 后面的参数 | 只能按关键字传 |
**kwargs | 收集额外关键字参数 |
示例:
def f(a, /, b, *, c):
print(a, b, c)
f(1, 2, c=3)
a 不能写成 a=1,c 必须写成 c=3。
五、调用时的解包
定义函数时的 *args 是收集参数,调用函数时的 * 是拆开序列:
def add(a, b, c):
return a + b + c
nums = [1, 2, 3]
print(add(*nums)) # 6
定义函数时的 **kwargs 是收集关键字参数,调用时的 ** 是拆开字典:
options = {"host": "localhost", "port": 3306}
connect(**options)
同一个符号在定义和调用两个场景里方向相反:定义时收进来,调用时拆出去。
六、常见误区与追问
记忆钩子:定义函数时,
*和**是“收”;调用函数时,*和**是“拆”。/管住左边只能位置传,单独的*管住右边只能关键字传。
- 误区:
args和kwargs是 Python 关键字。 真正有语法意义的是前面的*和**,名字可以换成*values、**options,只是工程里通常沿用args、kwargs提高可读性。 - 误区:
*args可以接收任意参数。 它只接收额外的位置参数,关键字参数会进入显式关键字参数或**kwargs;如果函数没有**kwargs,多余关键字参数会触发TypeError。 - 追问:为什么很多库喜欢把配置参数设计成仅限关键字参数? 因为
timeout=3、retries=2这类调用比connect(host, port, 3, 2)更不容易误读,也方便以后增加新配置而不破坏原有位置参数含义。 - 追问:
def f(a, *, b)里的*和*args有什么区别? 单独的*不收集参数,只是分隔符,表示后面的b必须按关键字传;*args既是分隔符,也会把额外位置参数收进元组。 - 追问:
/有什么实际价值? 它常用于标准库或稳定 API,避免调用者依赖参数名。例如dict.get(key, default=None)这类接口如果把内部参数名暴露成契约,以后重命名会更难。 - 误区:
**kwargs越多越灵活,所以越好。 过度使用会隐藏函数真实入参,降低 IDE 补全、静态检查和调用方可读性;稳定参数应显式写出来,扩展参数才适合放进**kwargs。
七、加强记忆
*args 收位置参数,**kwargs 收关键字参数;* 后面是仅限关键字参数,/ 前面是仅限位置参数。写函数签名时不要只追求灵活,稳定参数显式写出来,可选扩展才交给 *args 和 **kwargs。