Python 解包 operator 的 * 和 **
Python 解包操作符 * 和 ** 完全指南
在 Python 中,* 和 ** 不仅是乘法和乘方的符号,它们更是强大的“解包”操作符。解包能让你轻松地把列表、元组、字典等容器中的元素“拆开”,赋予到变量、函数参数或新的数据结构中。掌握解包是写出简洁、Pythonic 代码的关键。
什么是解包?
解包(Unpacking)就是从可迭代对象(如列表、元组、字符串)或映射对象(如字典)中提取元素,并把它们放置到需要单个元素的位置。* 用于序列解包,** 用于字典解包。
变量赋值中的 * 解包
最简单的解包发生在变量赋值时。如果可迭代对象的元素数量与变量数量匹配,可以直接一一赋值。
a, b, c = [1, 2, 3]
print(a, b, c) # 输出: 1 2 3
当元素数量不固定,或你只想提取一部分时,可以使用 * 来“贪婪”地捕获剩余的元素,形成一个列表。
捕获中间或末尾的变量
first, *middle, last = [10, 20, 30, 40, 50]
print(first) # 10
print(middle) # [20, 30, 40]
print(last) # 50
*head, tail = (1, 2, 3, 4, 5)
print(head) # [1, 2, 3, 4]
print(tail) # 5
* 变量总是接收一个列表,即使只捕获到一个元素。
忽略不需要的值
传统上用 _ 作为占位符,结合 *_ 可以忽略一批元素。
first, *_, last = "Hello"
print(first) # 'H'
print(last) # 'o'
嵌套序列的解包
解包也支持嵌套结构,只需按结构写出对应的括号即可。
data = ["Alice", (25, "Engineer"), [90, 95, 88]]
name, (age, job), [*scores] = data
print(name) # Alice
print(age) # 25
print(job) # Engineer
print(scores) # [90, 95, 88]
函数调用时的解包
在调用函数时,你可以用 * 将序列或可迭代对象的元素解包为位置参数,用 ** 将字典的键值对解包为关键字参数。
* 解包为位置参数
def greet(greeting, name):
return f"{greeting}, {name}!"
args = ["Hello", "Alex"]
print(greet(*args)) # Hello, Alex!
如果函数需要多个参数,列表的元素会按顺序传入。
** 解包为关键字参数
def create_profile(name, age, city):
return f"{name} is {age} years old from {city}."
info = {"name": "Emma", "age": 30, "city": "Berlin"}
print(create_profile(**info)) # Emma is 30 years old from Berlin.
混合使用解包
你可以同时使用 *args 和 **kwargs 来调用函数,Python 会先处理位置参数,再处理关键字参数。
def order(product, quantity, price, discount=0):
total = quantity * price * (1 - discount)
return f"Order: {quantity} x {product} = {total:.2f}"
positional = ["book", 3]
keywords = {"price": 15.99, "discount": 0.1}
print(order(*positional, **keywords)) # Order: 3 x book = 43.17
注意: 在函数调用中,* 和 ** 不能随意顺序插入。如果要混用,一般位置参数在前,关键字参数在后,解包操作必须放在所有单独的位置参数之后、关键字参数之前,且仅能出现一次 **。
函数定义中的 *args 和 **kwargs
在定义函数时,*args 允许你接收任意数量的位置参数(打包为一个元组),**kwargs 允许接收任意数量的关键字参数(打包为一个字典)。
*args:可变位置参数
def sum_all(*numbers):
total = 0
for n in numbers:
total += n
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(10, 20, 30, 40)) # 100
函数内部,numbers 是一个元组。你可以把已有列表解包后传入:
nums = [5, 10, 15]
print(sum_all(*nums)) # 30
**kwargs:可变关键字参数
def build_url(base, **params):
query = "&".join(f"{k}={v}" for k, v in params.items())
return f"{base}?{query}"
print(build_url("https://api.example.com/search", q="python", page=2))
# https://api.example.com/search?q=python&page=2
强制关键字参数(Keyword-only arguments)
在函数定义中,如果你在 *args 后面放参数,或者只是单独放一个 *,后面的形参都会变成“强制关键字参数”——调用时必须使用关键字形式。
def configure(host, *, port, debug=False):
print(f"Host: {host}, Port: {port}, Debug: {debug}")
# configure("localhost", 8080) # 报错:port 必须是关键字参数
configure("localhost", port=8080, debug=True) # 正确
这种写法能提高可读性,避免参数顺序混淆。
用 * 和 ** 合并容器
合并列表、元组等序列
* 可以解包多个可迭代对象,从而合并成一个新列表或元组。
list1 = [1, 2]
list2 = [3, 4, 5]
combined = [*list1, *list2]
print(combined) # [1, 2, 3, 4, 5]
tuple1 = (0, 1)
tuple2 = (2, 3)
combined_tuple = (*tuple1, *tuple2)
print(combined_tuple) # (0, 1, 2, 3)
也可以加入单独的元素:
middle = 0
numbers = [*list1, middle, *list2]
print(numbers) # [1, 2, 0, 3, 4, 5]
合并字典(Python 3.5+)
** 可以将多个字典合并成一个新字典。如果出现相同键,靠后的字典值会覆盖前面的。
defaults = {"color": "blue", "size": "M"}
user = {"size": "L", "theme": "dark"}
merged = {**defaults, **user}
print(merged) # {'color': 'blue', 'size': 'L', 'theme': 'dark'}
Python 3.9 引入了字典联合操作符 | 和 |=,同样可以实现合并,但 ** 解包在旧版本更通用。
解包操作符的限制与注意事项
-
*只能用于可迭代对象
尝试对非可迭代对象使用*会引发TypeError。# *42 # 错误 -
**只能用于映射对象(如字典)
它依赖于对象的.keys()方法,解包出的键必须是字符串才能作为关键字参数。 -
函数调用中
**的键必须与参数名匹配
如果字典包含函数未定义的键,会引发TypeError。 -
一个表达式中不能使用多个
**解包
{**d1, **d2}是允许的,因为它在字典字面量中。但在函数调用中只能出现一个**解包,否则语法错误。 -
不要混淆解包和乘法
*的歧义取决于上下文:作为单个前缀是解包,作为两个操作数之间的中缀是乘法或重复。 -
性能考虑
对于海量数据,频繁使用*解包合并序列会创建新的列表,相比extend或itertools.chain可能内存效率较低。但在日常开发中差异微乎其微。
常见错误与调试
-
SyntaxError: iterable unpacking cannot be used in comprehension
生成器/列表推导式中不允许使用*解包。例如[ *[1,2], x ]是语法错误。请在外面合并或使用chain。 -
变量数量不匹配且无
*捕获
a, b = [1, 2, 3]会抛出ValueError: too many values to unpack。使用a, *b = [1, 2, 3]来解决。 -
字典解包丢失信息
如果函数期望一个关键字参数,而字典中键的类型不是字符串,将引发TypeError。请确保键都是有效的 Python 标识符。
实战练习
尝试预测以下代码的输出,再运行验证:
# 练习 1
x, *y, z = range(5)
print(y)
# 练习 2
def show(a, b, c=10):
print(a, b, c)
data = (1, 2)
show(*data, c=20)
# 练习 3
conf = {"host": "localhost", "port": 5432}
defaults = {"port": 3306, "db": "test"}
final = {**defaults, **conf}
print(final)
# 练习 4
def register(name, *hobbies, **details):
print(f"{name} likes {hobbies}, details: {details}")
register("Bob", "chess", "hiking", age=25, location="NY")
总结
*解包序列(可迭代对象)到位置变量、位置参数,或合并序列。**解包字典到关键字参数,或合并字典。- 在赋值中,
*变量收集剩余项形成列表。 - 在函数定义中,
*args和**kwargs捕获任意数量的参数。 - 在函数调用中,
*iterable和**dict将容器展开为参数。
掌握解包能让你写出更加优雅、灵活、富于表达力的 Python 代码。多练习,让解包成为你的肌肉记忆吧!