不知道会有多少个参数的时候
大多数函数接收固定数量的参数。但偶尔你需要更灵活的写法:一个能接收任意多条消息的日志函数,一个把收到的参数原样转发给另一个函数的包装函数,一个接收配置但事先不知道调用者会传哪些选项的绘图函数。
Python 用参数列表中的两个特殊标记来处理这种情况:*args 和 **kwargs。
*args 收集位置参数
一个星号的意思是“把所有多出来的位置参数打包成一个元组”:
在函数内部,args 就是一个普通的元组。你可以遍历它、按索引取值、把它传给 len(),或者对它切片。
*args 通常和具名参数一起出现:
first 拿到第一个参数;*rest 把它后面的所有参数打包成一个元组。
**kwargs 收集关键字参数
两个星号对关键字参数做同样的事,把它们打包成一个字典:
在 describe 内部,kwargs 就是一个普通的字典。它的键是以字符串表示的关键字名称。
一起使用
你可以在同一个函数中同时使用两者。约定是 *args 写在 **kwargs 前面:
完整的参数顺序,从左到右是:
- 普通的位置参数(必填的或带默认值的)。
*args。- 仅限关键字参数(写在
*args之后的参数都必须按关键字传入)。 **kwargs。
title 拿到第一个位置参数。其余的位置参数都进入 tags。draft 必须按关键字传入(因为它写在 *tags 之后)。其他所有关键字参数都落进 metadata。
在调用处用 * 和 ** 解包
星号也可以反过来用:把一个序列或字典展开成一次调用的参数:
这在转发参数时特别好用:
wrapped 不需要知道 log 需要哪些参数。它只是把所有参数收集起来,再原样转发出去。这种写法在装饰器(一个更进阶的主题)和包装函数中随处可见。
什么时候不该用 *args 和 **kwargs
人很容易一兴奋就到处都用它们。有两点要注意:
它们会掩盖函数需要什么参数
如果你代码库里的每个函数都写成 def f(*args, **kwargs),调用方根本不知道哪些参数是有效的。能用具名参数就用具名参数,只让 *args/**kwargs 承载真正数量可变的输入,或者纯粹的参数转发。
错误信息会变得含糊
一个拼错的关键字名称,会在函数深处变成一个悄无声息的 None 或一个 KeyError,而不是在调用处立刻报出“unexpected keyword argument”。具名参数能给你好得多的反馈。
原则是:默认优先使用具名参数,只有当函数确实需要灵活,或者要把参数转发给另一个可调用对象时,才使用 *args/**kwargs。
一个实用的小例子
一个类似绘图辅助函数的工具,它用几个默认值包装了一个第三方函数:
*values 让调用者可以传入任意数量的值;**style 接住额外的配置,而不必把每个选项都写成具名参数。它很灵活,但并不晦涩,因为内部逻辑清楚地告诉你它会从 style 中读取哪些键。
小结
*args把多出来的位置参数打包成一个元组。**kwargs把多出来的关键字参数打包成一个字典。- 在调用处,
*seq和**dict按相反的方向解包。 - 参数必须按这个顺序出现:普通参数 →
*args→ 仅限关键字参数 →**kwargs。 - 不要滥用:能用具名参数的时候,具名参数更清楚。
下一篇:lambda,一种在行内编写临时小函数的方法。
常见问题
Python 中的 *args 是什么?
*args 会把所有多出来的位置参数收集到一个元组中。写成 def f(*args):,你就可以调用 f(1, 2, 3),得到的 args 是 (1, 2, 3)。args 这个名字只是约定俗成,你可以随便起名,但大家都写 *args。
Python 中的 **kwargs 是什么?
**kwargs 会把所有多出来的关键字参数收集到一个字典中。写成 def f(**kwargs):,你就可以调用 f(name='Ada', age=30),得到的 kwargs 是 {'name': 'Ada', 'age': 30}。*args 和 **kwargs 一起使用,可以让函数接受任意组合的参数。
一定要命名为 args 和 kwargs 吗?
不用,关键在星号,不在名字。*values 和 **options 的效果完全一样。但 args 和 kwargs 几乎是 Python 代码中通用的约定,除非有特别的理由要用更具描述性的名字,否则就沿用它们。