← 返回题目列表

Python 中 *args、**kwargs 和参数顺序怎么理解?

高频 中等 第 13 / 21 题 更新于 2026/07/27
argskwargs参数顺序函数签名

简化版

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

这里 timeoutretry 必须写名字,调用时更清晰。

完整版教学

一、函数参数为什么要分类

函数签名不只是为了让代码能跑,它还表达 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=1c 必须写成 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)

同一个符号在定义和调用两个场景里方向相反:定义时收进来,调用时拆出去。

六、常见误区与追问

记忆钩子:定义函数时,*** 是“收”;调用函数时,*** 是“拆”。/ 管住左边只能位置传,单独的 * 管住右边只能关键字传。

  • 误区:argskwargs 是 Python 关键字。 真正有语法意义的是前面的 ***,名字可以换成 *values**options,只是工程里通常沿用 argskwargs 提高可读性。
  • 误区:*args 可以接收任意参数。 它只接收额外的位置参数,关键字参数会进入显式关键字参数或 **kwargs;如果函数没有 **kwargs,多余关键字参数会触发 TypeError
  • 追问:为什么很多库喜欢把配置参数设计成仅限关键字参数? 因为 timeout=3retries=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