解包实参
调用函数时,如果实参已经存放在序列或字典中,可以用 * 和 ** 操作符将其"解包"为独立参数。解包是可变参数的逆操作:*args 在定义时收集参数,* 在调用时展开参数。
* 序列解包
当函数需要多个独立位置参数,但你的数据在一个列表或元组中时,用 * 解包:
def add_three(a, b, c):
return a + b + c
nums = [1, 2, 3]
print(add_three(*nums)) # 6,等价于 add_three(1, 2, 3)
* 可以作用于任何可迭代对象:列表、元组、字符串、range、生成器等:
print(add_three(*range(3))) # 0 + 1 + 2 = 3
print(add_three(*"abc")) # abc(字符串解包为 'a', 'b', 'c')
print(add_three(*{1, 2, 3})) # 6(集合无序,但元素是 1, 2, 3)
注意:序列长度必须与形参数量匹配,否则会报错:
# add_three(*[1, 2]) # TypeError: add_three() missing 1 required positional argument
# add_three(*[1, 2, 3, 4]) # TypeError: add_three() takes 3 positional arguments but 4 were given
** 字典解包
当函数需要多个关键字参数,但你的数据在一个字典中时,用 ** 解包:
def describe(name, age, city):
return f"{name},{age}岁,住在{city}"
info = {"name": "Alice", "age": 30, "city": "北京"}
print(describe(**info))
# Alice,30岁,住在北京
** 要求字典的键必须是字符串,且与函数形参名匹配。多余的键如果没有对应的 **kwargs 会报错:
# describe(**{"name": "Bob", "age": 25, "city": "上海", "extra": 1})
# TypeError: describe() got an unexpected keyword argument 'extra'
混合解包
* 和 ** 可以在同一次调用中混用,也可以与普通实参混用:
def func(a, b, c, d=4, e=5):
return (a, b, c, d, e)
args = [1, 2]
kwargs = {"d": 40, "e": 50}
print(func(*args, 3, **kwargs)) # (1, 2, 3, 40, 50)
混用时的规则与常规调用一致:位置参数在前,关键字参数在后。解包的位置参数视为位置参数,解包的关键字参数视为关键字参数。
# 合法:位置解包在前,普通位置在中,关键字解包在后
print(func(*[1, 2], 3, **{"d": 40}))
# 非法:关键字解包后接位置参数
# print(func(**{"d": 40}, 1, 2, 3)) # SyntaxError
与可变参数的对应关系
定义时的 *args 和调用时的 * 互为逆操作。定义时的 **kwargs 和调用时的 ** 互为逆操作:
def collect(*args, **kwargs):
return args, kwargs
# 正向:传入独立参数,收集为元组和字典
collected = collect(1, 2, 3, x=10, y=20)
print(collected)
# ((1, 2, 3), {'x': 10, 'y': 20})
# 逆向:用 * 和 ** 把元组和字典展开
args = (1, 2, 3)
kwargs = {"x": 10, "y": 20}
print(collect(*args, **kwargs))
# ((1, 2, 3), {'x': 10, 'y': 20})
这种对称性在函数包装和委托中非常有用。例如,写一个通用的日志包装器:
def logged(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__},参数: {args}, {kwargs}")
return func(*args, **kwargs) # 原样解包转发
return wrapper
@logged
def add(a, b):
return a + b
print(add(2, 3))
# 调用 add,参数: (2, 3), {}
# 5
解包与仅位置参数的交互
当函数有仅位置参数(/ 前)时,解包的位置参数仍然可以正常匹配:
def only_pos(a, b, /, c):
return a + b + c
print(only_pos(*[1, 2], 3)) # 6
print(only_pos(*[1, 2, 3])) # 6
但仅位置参数不能用关键字形式传入,即使通过字典解包也不行:
# only_pos(**{"a": 1, "b": 2, "c": 3})
# TypeError: only_pos() got some positional-only arguments passed as keyword arguments: 'a, b'
多重解包(Python 3.5+)
从 Python 3.5 开始,调用时可以使用多个 * 解包:
def sum_all(a, b, c, d, e):
return a + b + c + d + e
front = [1, 2]
back = [4, 5]
print(sum_all(*front, 3, *back)) # 15
同样,多个 ** 解包也允许,但后面的字典会覆盖前面的同名键:
def show(a, b):
return f"a={a}, b={b}"
d1 = {"a": 1, "b": 2}
d2 = {"b": 20}
print(show(**d1, **d2)) # a=1, b=20
常见错误
对不可迭代对象使用 *:
# add_three(*42) # TypeError: 'int' object is not iterable
字典键不是字符串:
# describe(**{1: "a"}) # TypeError: keywords must be strings
解包后参数重复:
# describe("Alice", **{"name": "Bob"})
# TypeError: describe() got multiple values for argument 'name'
小结
* 把可迭代对象解包为独立位置参数,** 把字典解包为独立关键字参数。两者可以混用、多次使用,与可变参数 *args/**kwargs 形成完美的对称。掌握解包机制,是编写灵活函数调用和包装器的关键。