可变参数
有时函数需要接收数量不确定的参数:处理任意多个数字的求和、接收任意多个配置项的日志函数等。Python 通过 *args 和 **kwargs 提供优雅的可变参数机制,同时支持 / 和 * 来限制参数的传递方式。
*args:接收多余的位置参数
在形参名前加 * 会把多余的位置参数收集为一个元组:
def total(*numbers):
result = 0
for n in numbers:
result += n
return result
print(total()) # 0
print(total(1, 2, 3)) # 6
print(total(1, 2, 3, 4, 5)) # 15
*args 是约定名称,可以改,但强烈建议保留。args 在函数内部是一个普通元组,支持索引、迭代、切片等所有元组操作:
def first_and_rest(first, *rest):
return first, rest
print(first_and_rest(10, 20, 30)) # (10, (20, 30))
*args 前面可以有普通位置参数,这些参数按顺序优先匹配,剩余的才进入 *args:
def tag_list(tag, *items):
return [f"[{tag}] {item}" for item in items]
print(tag_list("INFO", "启动", "连接成功", "就绪"))
# ['[INFO] 启动', '[INFO] 连接成功', '[INFO] 就绪']
**kwargs:接收多余的关键字参数
在形参名前加 ** 会把多余的关键字参数收集为一个字典:
def build_profile(name, **kwargs):
profile = {"name": name}
profile.update(kwargs)
return profile
print(build_profile("Alice", age=30, city="Beijing"))
# {'name': 'Alice', 'age': 30, 'city': 'Beijing'}
**kwargs 同样是一个约定名称。内部字典的键是字符串(参数名),值是传入的对象。关键字参数的顺序在 Python 3.7+ 中得以保留。
*args 与 **kwargs 的组合
两者可以一起使用,*args 必须在 **kwargs 之前:
def log_event(level, *args, **kwargs):
msg = " ".join(str(a) for a in args)
extras = ", ".join(f"{k}={v}" for k, v in kwargs.items())
if extras:
msg += f" ({extras})"
return f"[{level}] {msg}"
print(log_event("ERROR", "连接失败", code=500, retry=3))
# [ERROR] 连接失败 (code=500, retry=3)
*args 后的仅限关键字参数
*args 后面的普通参数只能通过关键字传递,不能通过位置传递:
def concat(*args, sep=" "):
return sep.join(str(a) for a in args)
print(concat("a", "b", "c")) # a b c
print(concat("a", "b", "c", sep="-")) # a-b-c
# concat("a", "b", "c", "-") # 非法!sep 必须关键字传参
# TypeError: concat() takes from 0 to 3 positional arguments but 4 were given
这个特性非常有用:它确保调用者必须显式指定某些参数的含义,避免位置参数的歧义。
仅位置参数(Python 3.8+)
在参数列表中用 / 分隔,/ 之前的参数只能按位置传递,不能用关键字:
def pow(base, exp, /):
return base ** exp
print(pow(2, 3)) # 8
# pow(base=2, exp=3) # TypeError: pow() got some positional-only arguments passed as keyword arguments
仅位置参数适用于以下场景:参数名没有语义价值(如数学函数)、需要强制调用者遵守参数顺序、或者参数名可能变化(API 兼容性)。
仅限关键字参数(Python 3.8+)
在参数列表中用 * 分隔,* 之后的参数必须按关键字传递:
def draw_point(x, y, *, color="black", size=1):
return f"点({x}, {y}) 颜色={color} 大小={size}"
print(draw_point(10, 20, color="red", size=2)) # 合法
# draw_point(10, 20, "red", 2) # 非法!color 和 size 必须关键字传参
三种参数方式的组合
一个函数可以同时使用仅位置、位置或关键字、仅限关键字参数:
def complex_func(pos_only, /, pos_or_kwd, *, kwd_only):
return (pos_only, pos_or_kwd, kwd_only)
print(complex_func(1, 2, kwd_only=3)) # 合法
print(complex_func(1, pos_or_kwd=2, kwd_only=3)) # 合法
# complex_func(pos_only=1, 2, kwd_only=3) # 非法!pos_only 只能位置
# complex_func(1, 2, 3) # 非法!kwd_only 只能关键字
常见错误
*args 和 **kwargs 顺序写反:
# def bad(**kwargs, *args): # SyntaxError: invalid syntax
# pass
试图把 *args 当作列表修改(它是元组,不可变):
def broken(*args):
args.append(1) # AttributeError: 'tuple' object has no attribute 'append'
在 **kwargs 中使用非字符串键(调用时参数名自动转为字符串,但手动构造字典解包时可能出错):
def f(**kwargs):
print(kwargs)
# f(**{1: "a"}) # TypeError: keywords must be strings
小结
*args 收集多余位置参数为元组,**kwargs 收集多余关键字参数为字典。*args 后的参数必须关键字传递,/ 前的参数必须位置传递,* 后的参数必须关键字传递。合理组合这些机制,可以设计出既灵活又清晰的函数接口。