发布于2026-06-30 阅读(0)
扫一扫,手机访问
学习笔记:单星号 * 展开列表/元组为位置参数,双星号 ** 展开字典为关键字参数。

在 Python 函数调用里,* 和 ** 都用来干同一件事——「解包」,也就是把容器里的元素拆开,挨个传给函数。怎么拆、拆完变成什么,得看是单星号还是双星号。
| 符号 | 解包对象 | 变成 |
|---|---|---|
* | 列表、元组等序列 | 位置参数 func(1, 2, 3) |
** | 字典 | 关键字参数 func(a=1, b=2) |
一句话记牢:一个星号管位置,两个星号管关键字。好,我们一个个来看。
假设你有一个函数,需要三个参数:名字、年龄、城市。你手上恰好有一个列表,装的就是这三个值。这时候,* 就派上用场了。
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)
你可以在调用时把普通参数和解包混在一起,但要注意顺序。
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
反过来,在定义函数时,*args 是用来“收”的——把多出来的位置参数全部收集成一个元组。
def foo(*args):
print(args)
print(type(args))
foo(1, 2, 3)
# (1, 2, 3)
#
| 场景 | * 的作用 |
|---|---|
调用时 func(*[1,2,3]) | 把序列 拆开 传入 |
定义时 def f(*args) | 把多出来的位置参数 收进 元组 |
不只是列表和元组,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
如果说 * 拆的是列表,那 ** 拆的就是字典。它会将字典的每个键值对,原封不动地变成关键字参数。
def greet(name, age):
print(f"name={name}, age={age}")
info = {"name": "Alice", "age": 20}
greet(**info)
# 等价于 greet(name="Alice", age=20)
关键:字典的 key 必须和函数的参数名一致,否则会报错。
在定义函数时,**kwargs 则把所有关键字参数收进一个字典里。
def foo(**kwargs):
print(kwargs)
foo(a=1, b=2)
# {'a': 1, 'b': 2}
| 场景 | ** 的作用 |
|---|---|
调用时 func(**d) | 把 dict 拆开 传入 |
定义时 def f(**kwargs) | 把关键字参数 收进 dict |
在实际项目中,** 最常见的用法之一,就是从配置字典中解包出参数传给某个类或函数。比如下面的例子,从字典中过滤出有效配置,然后解包传给 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})
三步走:
** → 展开成 Configuration(字段=值, ...)这是 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:所有关键字参数 → 字典调用时也一样,可以先传位置参数,再用 * 解包一个序列,然后用 ** 解包一个字典。顺序有讲究:
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
顺序规则(调用时):
位置参数 → *解包序列 → 关键字参数 → **解包字典
还有一个经典用法:在函数定义里放一个单独的 *,表示它后面的参数必须用关键字传入,不能按位置来。
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),让代码意图更清晰。
a = [1, 2] b = [3, 4] c = [*a, *b] # [1, 2, 3, 4]
a = {"x": 1}
b = {"y": 2}
c = {**a, **b} # {"x": 1, "y": 2}
b = {"x": 99}
{**a, **b} # {"x": 99} 后面的覆盖前面的
写装饰器或者包装函数时,这种写法能原封不动地把参数转发给原始函数,省心又省力。
def wrapper(*args, **kwargs):
print("调用前")
result = original_func(*args, **kwargs) # 原样转发
print("调用后")
return result
单星号 * | 双星号 ** | |
|---|---|---|
| 解包对象 | list、tuple、range 等序列 | dict |
| 调用时变成 | 位置参数 | 关键字参数 |
| 定义时收集 | *args → tuple | **kwargs → dict |
| 典型例子 | func(*[1, 2, 3]) | func(**{"a": 1}) |
| 元素要求 | 按顺序对应参数 | key 必须是合法参数名 |
这些坑,相信不少人都踩过。我们快速过一遍,避开就好。
def foo(a, b):
pass
foo(*[1, 2, 3]) # ❌ TypeError: too many arguments
foo(**{"a": 1}) # ❌ TypeError: missing b
foo(**{"max-step": 5}) # ❌ key 带横线,不能当参数名
foo(**{"max_step_num": 5}) # ✅
def foo(a, b):
pass
foo(1, *[2, 3]) # ✅
foo(1, b=2, *[3]) # ❌ b 传了两次
foo(1, **{"a": 99}) # ❌ a 传了两次
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 | 把多出来的参数 收进 容器 |
黑话版:一个星号拆出位置参数,两个星号拆出关键字参数。记住这一点,剩下的就是多写多练了。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8