商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Python利用*与**进行解包的完整教学

Python利用*与**进行解包的完整教学

  发布于2026-06-30 阅读(0)

扫一扫,手机访问

学习笔记:单星号 * 展开列表/元组为位置参数,双星号 ** 展开字典为关键字参数。

Python利用*与**进行解包的完整教学

前言

在 Python 函数调用里,*** 都用来干同一件事——「解包」,也就是把容器里的元素拆开,挨个传给函数。怎么拆、拆完变成什么,得看是单星号还是双星号。

符号解包对象变成
*列表、元组等序列位置参数 func(1, 2, 3)
**字典关键字参数 func(a=1, b=2)

一句话记牢:一个星号管位置,两个星号管关键字。好,我们一个个来看。

一、单星号*— 展开序列

1.1 调用函数时:*展开列表/元组

假设你有一个函数,需要三个参数:名字、年龄、城市。你手上恰好有一个列表,装的就是这三个值。这时候,* 就派上用场了。

def greet(name, age, city):
    print(f"{name}, {age}岁, 来自{city}")

args = ["Alice", 20, "Beijing"]

greet(*args)
# 等价于 greet("Alice", 20, "Beijing")

*args 把列表里的元素按顺序当作位置参数传入,一个不多,一个不少。元组也一样,本质上是序列解包。

point = (10, 20)

def draw(x, y):
    print(x, y)

draw(*point)   # draw(10, 20)

1.2 和普通参数混用

你可以在调用时把普通参数和解包混在一起,但要注意顺序。

def foo(a, b, c):
    print(a, b, c)

foo(1, *[2, 3])       # foo(1, 2, 3)
foo(*[1, 2], 3)       # ❌ 语法错误,* 解包段必须在最后或单独使用
foo(1, *[], 3)        # ❌ 同上

正确混用的方式是这样——把解包放在普通参数的后面:

rest = [2, 3]
foo(1, *rest)          # ✅ a=1, b=2, c=3

1.3 定义函数时:*args收集多余位置参数

反过来,在定义函数时,*args 是用来“收”的——把多出来的位置参数全部收集成一个元组。

def foo(*args):
    print(args)
    print(type(args))

foo(1, 2, 3)
# (1, 2, 3)
# 
场景* 的作用
调用时 func(*[1,2,3])把序列 拆开 传入
定义时 def f(*args)把多出来的位置参数 收进 元组

1.4 解包其他序列

不只是列表和元组,range 对象、字符串……只要是序列,都能解。但要注意,不是所有地方都能直接解包。

nums = range(1, 4)     # range 对象
list(*nums)            # ❌ range 不能直接 * 给 list()
print(*nums)           # ✅ print(1, 2, 3)

def total(a, b, c):
    return a + b + c

total(*[10, 20, 30])   # 60

二、双星号**— 展开字典

2.1 调用函数时:**展开 dict

如果说 * 拆的是列表,那 ** 拆的就是字典。它会将字典的每个键值对,原封不动地变成关键字参数。

def greet(name, age):
    print(f"name={name}, age={age}")

info = {"name": "Alice", "age": 20}

greet(**info)
# 等价于 greet(name="Alice", age=20)

关键:字典的 key 必须和函数的参数名一致,否则会报错。

2.2 定义函数时:**kwargs收集多余关键字参数

在定义函数时,**kwargs 则把所有关键字参数收进一个字典里。

def foo(**kwargs):
    print(kwargs)

foo(a=1, b=2)
# {'a': 1, 'b': 2}
场景** 的作用
调用时 func(**d)把 dict 拆开 传入
定义时 def f(**kwargs)把关键字参数 收进 dict

2.3 项目里的真实用法

在实际项目中,** 最常见的用法之一,就是从配置字典中解包出参数传给某个类或函数。比如下面的例子,从字典中过滤出有效配置,然后解包传给 Configuration 类。

filtered = {"max_step_num": 5}

cls(**filtered)
# 等价于 Configuration(max_step_num=5)

完整链条(src/config/configuration.py)通常是这样:

return cls(**{k: v for k, v in values.items() if v})

三步走:

  1. 字典推导式 → 过滤得到有效配置
  2. ** → 展开成 Configuration(字段=值, ...)
  3. 未出现的字段 → 用 dataclass 默认值

三、*和**一起用

3.1 定义函数:*args+**kwargs

这是 Python 中定义“万能函数”的标准写法——既能处理任意数量的位置参数,也能处理任意数量的关键字参数。

def foo(a, b, *args, **kwargs):
    print("a, b =", a, b)
    print("args  =", args)
    print("kwargs=", kwargs)

foo(1, 2, 3, 4, x=10, y=20)
# a, b = 1 2
# args  = (3, 4)
# kwargs= {'x': 10, 'y': 20}
  • a, b:前两个位置参数
  • *args:多出来的位置参数 → 元组
  • **kwargs:所有关键字参数 → 字典

3.2 调用函数:同时解包

调用时也一样,可以先传位置参数,再用 * 解包一个序列,然后用 ** 解包一个字典。顺序有讲究:

def foo(a, b, c, x, y):
    print(a, b, c, x, y)

pos = [3, 4]
kw = {"x": 10, "y": 20}

foo(1, 2, *pos, **kw)
# foo(1, 2, 3, 4, x=10, y=20)
# 输出: 1 2 3 4 10 20

顺序规则(调用时):

位置参数 → *解包序列 → 关键字参数 → **解包字典

3.3 强制关键字参数(Python 3+)

还有一个经典用法:在函数定义里放一个单独的 *,表示它后面的参数必须用关键字传入,不能按位置来。

def connect(host, port, *, timeout=30, ssl=True):
    ...

connect("localhost", 3306, timeout=60)   # ✅
connect("localhost", 3306, 60)             # ❌ timeout 必须用 timeout=60

这和你们项目中 @dataclass(kw_only=True) 的思路一致:创建对象时必须写 Configuration(max_step_num=3),让代码意图更清晰。

四、*/**的其他用法(补充)

4.1 合并列表 / 元组

a = [1, 2]
b = [3, 4]
c = [*a, *b]           # [1, 2, 3, 4]

4.2 合并字典

a = {"x": 1}
b = {"y": 2}
c = {**a, **b}         # {"x": 1, "y": 2}

b = {"x": 99}
{**a, **b}             # {"x": 99}  后面的覆盖前面的

4.3 函数传参转发(包装器常见写法)

写装饰器或者包装函数时,这种写法能原封不动地把参数转发给原始函数,省心又省力。

def wrapper(*args, **kwargs):
    print("调用前")
    result = original_func(*args, **kwargs)   # 原样转发
    print("调用后")
    return result

五、*vs**对照表

单星号 *双星号 **
解包对象list、tuple、range 等序列dict
调用时变成位置参数关键字参数
定义时收集*args → tuple**kwargs → dict
典型例子func(*[1, 2, 3])func(**{"a": 1})
元素要求按顺序对应参数key 必须是合法参数名

六、常见误区

这些坑,相信不少人都踩过。我们快速过一遍,避开就好。

误区 1:参数个数对不上

def foo(a, b):
    pass

foo(*[1, 2, 3])   # ❌ TypeError: too many arguments
foo(**{"a": 1})   # ❌ TypeError: missing b

误区 2:**的 key 不是合法标识符

foo(**{"max-step": 5})   # ❌ key 带横线,不能当参数名
foo(**{"max_step_num": 5})  # ✅

误区 3:重复传参

def foo(a, b):
    pass

foo(1, *[2, 3])          # ✅
foo(1, b=2, *[3])        # ❌ b 传了两次
foo(1, **{"a": 99})      # ❌ a 传了两次

误区 4:混淆「解包」和「乘法」

2 * 3        # 乘法 → 6
func(*[2, 3])  # 解包 → 传入两个参数 2 和 3

上下文不同:在函数调用或定义参数列表里,* / ** 是解包;在表达式里是运算。千万别弄混。

七、小练习

光看不练,说忘就忘。这几个小练习可以跑一跑,巩固一下。

# 练习 1:用 * 解包
def add(a, b, c):
    return a + b + c
print(add(*[1, 2, 3]))   # 6

# 练习 2:用 ** 解包
def profile(name, age):
    return f"{name}-{age}"
print(profile(**{"name": "Tom", "age": 18}))  # Tom-18

# 练习 3:模拟项目
def fake_config(**kwargs):
    print(kwargs)
fake_config(**{"max_step_num": 5, "locale": "zh-CN"})

八、小结

最后,用三行代码总结全文核心:

foo(*[1, 2, 3])              # 位置:foo(1, 2, 3)
foo(**{"a": 1, "b": 2})      # 关键字:foo(a=1, b=2)
cls(**{"max_step_num": 5})   # Configuration(max_step_num=5)
记住
*序列 → 位置参数
**字典 → 关键字参数
调用时把容器 展开 传进去
定义时 *args / **kwargs把多出来的参数 收进 容器

黑话版:一个星号拆出位置参数,两个星号拆出关键字参数。记住这一点,剩下的就是多写多练了。

本文转载于:https://www.jb51.net/python/364778dx5.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注