# 位置参数、关键字参数、*args和**kwargs怎么用?
面试官问:"Python函数参数中的*args和**kwargs是什么?什么时候用?"
很多人只知道"*args接收多余的位置参数,**kwargs接收多余的关键字参数",但说不清楚调用时如何匹配、定义时如何限制参数形式,以及解包时会发生什么。
# 简要回答
Python函数参数分为位置参数、关键字参数、可变位置参数*args、可变关键字参数**kwargs,以及仅限位置参数和仅限关键字参数。
位置参数按顺序匹配,调用时按位置传值或用参数名指定。关键字参数必须用参数名传值。*args收集多余的位置参数到元组,**kwargs收集多余的关键字参数到字典。
调用时,可以用*解包序列为位置参数,用**解包字典为关键字参数。
定义时,单独的/之前的参数只能按位置传递,单独的*之后的参数只能按关键字传递。
# 详细回答
# 位置参数与关键字参数
def greet(name, age):
print(f"{name} is {age} years old")
greet("Alice", 25) # 位置传递
greet(age=25, name="Alice") # 关键字传递
greet("Alice", age=25) # 混合传递,位置参数必须在前
2
3
4
5
6
位置参数按顺序匹配,关键字参数按名字匹配。 混合传递时,位置参数必须在关键字参数之前。
# *args收集多余位置参数
def sum_all(*args):
return sum(args)
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
2
3
4
5
*args把所有多余的位置参数收集成一个元组。函数内可以通过args访问这个元组。
# **kwargs收集多余关键字参数
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Bob", age=30, city="Beijing")
# name: Bob
# age: 30
# city: Beijing
2
3
4
5
6
7
8
**kwargs把所有多余的关键字参数收集成一个字典。函数内可以通过kwargs访问这个字典。
# 同时使用多种参数
函数参数的定义顺序必须遵守:
def func(pos1, pos2, /, pos_or_kw, *args, kw_only, **kwargs):
pass
2
| 位置 | 参数类型 | 说明 |
|---|---|---|
/之前 | 仅限位置参数 | 只能按位置传递 |
/之后、*args之前 | 位置或关键字参数 | 可以按位置或关键字传递 |
*args | 可变位置参数 | 收集多余位置参数 |
*args之后、**kwargs之前 | 仅限关键字参数 | 只能按关键字传递 |
**kwargs | 可变关键字参数 | 收集多余关键字参数 |
示例:
def example(a, b, /, c, d, *args, e, f=10, **kwargs):
print(f"a={a}, b={b}, c={c}, d={d}")
print(f"args={args}")
print(f"e={e}, f={f}")
print(f"kwargs={kwargs}")
example(1, 2, 3, 4, 5, 6, e=7, f=8, g=9, h=10)
# a=1, b=2, c=3, d=4
# args=(5, 6)
# e=7, f=8
# kwargs={'g': 9, 'h': 10}
2
3
4
5
6
7
8
9
10
11
a和b必须按位置传递,c和d可以按位置或关键字传递,5和6被*args收集,e和f必须用关键字传递,g和h被**kwargs收集。

# 仅限关键字参数
单独的*之后的参数只能用关键字传递:
def create_user(name, *, email, age):
return {"name": name, "email": email, "age": age}
user = create_user("Alice", email="alice@example.com", age=25)
# 正确
user = create_user("Alice", "alice@example.com", 25)
# TypeError: create_user() takes 1 positional argument but 3 were given
2
3
4
5
6
7
8
这种设计强制调用方明确参数含义,提高可读性。
# 仅限位置参数
单独的/之前的参数只能按位置传递:
def divide(a, b, /):
return a / b
print(divide(10, 2)) # 5.0
print(divide(a=10, b=2))
# TypeError: divide() got some positional-only arguments passed as keyword arguments: 'a, b'
2
3
4
5
6
7
仅限位置参数是Python 3.8引入的语法,用于保护参数名不被暴露,允许后续修改参数名而不破坏调用方代码。
# 参数解包
调用时可以用*解包序列,用**解包字典:
def add(a, b, c):
return a + b + c
values = [1, 2, 3]
print(add(*values)) # 6,等价于add(1, 2, 3)
params = {"a": 1, "b": 2, "c": 3}
print(add(**params)) # 6,等价于add(a=1, b=2, c=3)
2
3
4
5
6
7
8
解包时,序列长度必须与位置参数数量匹配,字典的键必须与参数名匹配。
# 知识拓展
Q1:*args和**kwargs的名字可以改吗?
可以。*和**是语法,args和kwargs只是约定俗成的名字。可以写成*numbers或**options,但不建议改,因为会降低代码可读性。
Q2:为什么*args必须在**kwargs之前?
因为调用时位置参数必须在关键字参数之前。*args收集位置参数,**kwargs收集关键字参数,定义顺序必须匹配调用顺序。
Q3:如何限制*args或**kwargs的内容?
函数内检查:
def create_user(*args, **kwargs):
if len(args) > 0:
raise TypeError("create_user() takes 0 positional arguments")
allowed_keys = {"name", "email", "age"}
for key in kwargs:
if key not in allowed_keys:
raise TypeError(f"Unexpected keyword argument: {key}")
2
3
4
5
6
7
或者改用明确参数,不用**kwargs。
Q4:调用时能同时用*和**解包吗?
可以:
args = [1, 2]
kwargs = {"c": 3}
print(add(*args, **kwargs)) # 6
2
3
但注意不要重复传递同一个参数,否则会抛出TypeError。
Q5:什么时候用*args和**kwargs?
- 包装其他函数时,用于透传参数。
- 需要接受任意数量参数时(如
print、max)。 - 写装饰器时,保持被装饰函数的签名。
不要滥用。能用明确参数就用明确参数,提高可读性和IDE提示能力。
评论
验证登录状态...