星号表达式与解包

*args:函数接收任意位置参数

作用:把额外的位置参数收集为一个 tuple

1
2
3
4
5
6
def summarize(*args):
# args 是 tuple
return sum(args)

print(summarize(1, 2, 3)) # 6
print(summarize()) # 0

要点

  • *args 可以出现在参数列表中任意位置,但惯例写在普通位置参数之后、关键字参数之前:def f(a, b, *args, kw=0):
  • 在函数内部,argstuple(不可变)。
  • 多个星号参数不允许(只能有一个 * 捕获位置参数)。

常见用法

  • 聚合任意数量的位置参数(统计、拼接等)。
  • 与普通参数组合,做默认或强制参数数量检查。

注意:

  • *args 捕获的是位置参数,不会捕获以 name=value 形式传入的关键字参数(那些会进 **kwargs)。

**kwargs:函数接收任意关键字参数

作用:把额外的关键字参数收集为一个 dict

1
2
3
4
5
6
def show(**kwargs):
# kwargs 是 dict
for k, v in kwargs.items():
print(k, v)

show(a=1, b=2) # 打印 a 1 b 2

要点

  • **kwargs 必须放在参数列表的最后(在 *args 之后)。
  • 内部是 dict,键为传入的参数名(字符串),值为对应值。
  • 可结合显式关键字参数:def f(x, **kw):

常见用法

  • 接受灵活的配置选项。
  • 把参数透传给别的函数:other_func(**kwargs)

坑 / 注意

  • 在调用时 ** 解包的字典中键必须是字符串且是合法的标识符(否则作为关键字参数会失败);例如 f(**{'1': 2}) 会报错(非法关键字名)。
  • 函数调用时,如果同时显式传入了和 **dict 中重复的关键字,会产生 “multiple values for argument” 的错误(或最后一个覆盖,视目标函数签名而定)—小心显式位置/关键字与解包字典的冲突。

用 * / ** 进行解包

作用:把序列或映射展开成函数的参数。

1
2
3
4
5
6
7
8
def f(a, b, c):
return a + b + c

t = (1, 2, 3)
d = {'a': 1, 'b': 2, 'c': 3}

print(f(*t)) # 等价 f(1,2,3) -> 6
print(f(**d)) # 等价 f(a=1,b=2,c=3) -> 6

要点

  • *iterable:将可迭代对象按位置展开(数量 & 顺序必须匹配位置参数)。
  • **mapping:将映射展开为关键字参数(映射的 keys 必须为合适的标识符,与目标参数名匹配)。
  • 可以同时用:f(*args, **kwargs)

进阶

  • Python 支持在函数调用时使用多个 ***(从 Python 3.5+ PEP 448),例如 f(*a, *b)f(**d1, **d2) —— 最终传入的参数由左向右合并(后面的覆盖前面的,注意与显式传参冲突的情况)。

坑 / 注意

  • 如果函数签名已经通过位置或关键字给定了同名参数,额外的解包可能会导致 TypeError: multiple values for argument 'x'
  • 合并大量大字典/列表会临时分配新对象,注意内存开销。

用 * / ** 进行赋值

作用:在赋值时收集中间元素到列表。

1
2
3
4
5
6
7
8
a, *middle, c = [0, 1, 2, 3, 4]
# a = 0, middle = [1,2,3], c = 4

head, *rest = (10, 20, 30)
# head=10, rest=[20,30]

# 注意只有一个 starred target 被允许在左侧
# a, *b, *c = range(5) --> SyntaxError

要点

  • 左侧允许一个 *name 用于捕获剩余元素,结果是 list(即使原来是 tuple/iterable)。
  • * 可以出现在任意位置:首、中、尾(如 *head, tail = ...)。
  • 一个 starred target 被允许在赋值语句左侧。

常见用法

  • 取首尾、弹性匹配。
  • 在递归函数中将可迭代拆成首元素 + 剩余列表。

用 * / ** 合并容器

列表/元组合并(Python 3.5+)

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

字典合并(Python 3.5+ 用 **,3.9+ 有 |

1
2
3
d1 = {'x': 1}
d2 = {'y': 2, 'x': 9}
merged = {**d1, **d2} # {'x':9, 'y':2} 后面的覆盖前面的

要点

  • 在字典字面量中使用 ** 是很常用的模式来合并/覆盖配置。
  • 多个 * / ** 按顺序展开,后者覆盖前者同名键。

坑 / 注意

  • 合并大量大容器会产生临时对象(内存/性能考虑)。
  • 字面量解包要求被解包对象是可迭代或映射,否则会报错。

注意事项

  • *args 内部是 tuple(不可变)——如果需要列表操作,list(args) 一次性复制。
  • {**d1, **d2} 会创建新 dict,不是原地修改(如果想就地修改用 d1.update(d2))。
  • [*a, *b] 创建新列表;若想减少内存分配,考虑使用迭代器或 itertools.chain

常见模式

  1. 透传参数(decorator / wrapper):
1
2
3
4
5
def wrapper(func):
def inner(*args, **kwargs):
# 做点事情
return func(*args, **kwargs)
return inner
  1. 合并配置:
1
2
3
defaults = {'timeout': 30, 'retries': 3}
user = {'retries': 5}
cfg = {**defaults, **user} # user 覆盖 defaults
  1. 可变参数收集与强制关键词参数:
1
2
3
def f(a, b, *, verbose=False):
...
# * 将后面的参数强制为关键字参数: def f(a, b, *args, verbose=False) 的一种变体
  1. 灵活地拆分序列:
1
2
first, *rest = data
last = rest.pop() if rest else None

常见问题

  • SyntaxError:在赋值左侧用多个 *
  • TypeError: f() got multiple values for argument ‘x’:通常是位置参数、命名参数与解包字典的键重复导致,检查调用处的参数来源。
  • KeyError / invalid keywordf(**d) 的字典键必须是字符串且是合法标识符(不能含空格、不能以数字开头等);若键不是合法标识符,不能直接 ** 解包到关键字参数。
  • 性能:避免在热路径频繁创建大临时容器([*a, *b]{**d1, **d2});改用迭代器或 in-place 更新(extend/update)如果可行。