Python 解包 operator 的 * 和 **

FreeGuideOnline 最新 2026-07-06

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 引入了字典联合操作符 ||=,同样可以实现合并,但 ** 解包在旧版本更通用。


解包操作符的限制与注意事项

  1. * 只能用于可迭代对象
    尝试对非可迭代对象使用 * 会引发 TypeError

    # *42  # 错误
    
  2. ** 只能用于映射对象(如字典)
    它依赖于对象的 .keys() 方法,解包出的键必须是字符串才能作为关键字参数。

  3. 函数调用中 ** 的键必须与参数名匹配
    如果字典包含函数未定义的键,会引发 TypeError

  4. 一个表达式中不能使用多个 ** 解包
    {**d1, **d2} 是允许的,因为它在字典字面量中。但在函数调用中只能出现一个 ** 解包,否则语法错误。

  5. 不要混淆解包和乘法
    * 的歧义取决于上下文:作为单个前缀是解包,作为两个操作数之间的中缀是乘法或重复。

  6. 性能考虑
    对于海量数据,频繁使用 * 解包合并序列会创建新的列表,相比 extenditertools.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 代码。多练习,让解包成为你的肌肉记忆吧!