吃透 Python 函数参数!*args/kwargs 用法 + 避坑指南咱们写 Python 函数时,是不是常遇到这种麻烦:想做个求和函数,有时候加 2 个数,有时候加 5 个;想做个问候函数,有时候问候 1 个人,有时候问候 10 个。总不能为每种情况写一个函数吧?
这时候就得靠 *args 和 **kwargs 这俩 “神器” 了 —— 它们能让函数轻松处理 “不确定个数” 的参数。今天咱们从基础到进阶,把这俩玩意儿彻底嚼碎,再聊聊最容易踩的坑和面试常考的点,保证你学完就能用!
一、先搞懂:*args 是啥?怎么用?*args 全称是 “不定长位置参数”,简单说就是:把调用函数时传的 “位置参数”,全都打包成一个元组(tuple),方便函数内部处理。
1. 基础用法:动态接收多个位置参数比如写个 “动态求和函数”,不管传几个数字都能算总和:
代码语言:python复制# 定义带 *args 的函数
def add(*args):
# 先打印 args 的类型和内容,让大家看明白
print("args 的类型:", type(args)) # 输出
print("args 的内容:", args) # 传的参数会变成元组里的元素
total = 0
# 遍历元组,把所有数加起来
for num in args:
total += num
return total
# 测试:传 2 个、3 个、1 个参数都能行
print("求和结果:", add(1, 2)) # 输出:args 内容 (1,2),求和结果 3
print("求和结果:", add(3, 4, 5)) # 输出:args 内容 (3,4,5),求和结果 12
print("求和结果:", add(10)) # 输出:args 内容 (10,),求和结果 10
print("求和结果:", add()) # 输出:args 内容 (),求和结果 0(空元组遍历后 total 还是 0)2. 关键特点:只能接收 “位置参数”(就是不用 key=value 形式传的参数);打包后是 元组,所以能遍历、取长度(len(args)),但不能修改(元组不可变);args 这个名字可以改(比如 *nums、*params),但行业约定俗成用 *args,别人看代码更懂。二、再搞懂:**kwargs 是啥?怎么用?**kwargs 全称是 “不定长关键字参数”,和 *args 对应:把调用函数时传的 “关键字参数”(key=value形式),全都打包成一个字典(dict),键是参数名,值是参数值。
1. 基础用法:动态接收多个关键字参数比如写个 “打印用户信息” 的函数,用户可能传姓名、年龄、性别,也可能只传姓名,都能处理:
代码语言:python复制# 定义带 **kwargs 的函数
def print_user(**kwargs):
print("kwargs 的类型:", type(kwargs)) # 输出
print("kwargs 的内容:", kwargs) # 传的 key=value 会变成字典的键值对
# 遍历字典,打印用户信息(用 get 避免键不存在报错)
print("姓名:", kwargs.get("name", "未知")) # 没传 name 就显示“未知”
print("年龄:", kwargs.get("age", "未知"))
print("性别:", kwargs.get("gender", "未知"))
print("-" * 20) # 分隔线,方便看多个测试结果
# 测试1:传 3 个关键字参数
print_user(name="小明", age=18, gender="男")
# 测试2:只传 2 个关键字参数
print_user(name="小红", age=19)
# 测试3:传额外的参数(函数里没用到,但不报错)
print_user(name="小刚", age=20, gender="男", city="北京")运行结果会清晰看到:kwargs 是字典,没传的参数会显示 “未知”,额外传的参数也不会报错(只是没用到)。
2. 关键特点:只能接收 “关键字参数”(必须用 key=value 形式);打包后是 字典,所以能按键取值、遍历,也能修改(字典可变);kwargs 名字也能改(比如 **user_info),但同样推荐用 **kwargs 符合约定。三、重中之重:函数参数的 “正确顺序”很多人用 *args/**kwargs 报错,都是因为参数顺序搞反了!Python 对函数参数的顺序有严格要求,错一点就会报 TypeError。
1. 正确顺序公式(记死!) 普通位置参数 → *args → 普通关键字参数 → kwargs**
比如定义一个包含所有参数类型的函数,顺序必须是这样:
代码语言:python复制def func(普通位置参数, *args, 普通关键字参数, **kwargs):
pass2. 用表格理清顺序(一看就懂)参数类型
位置顺序
示例写法
核心规则
普通位置参数
1
def func(a, b, *args, ...):
必须在 *args 前面,调用时按位置传
*args(不定长)
2
def func(a, b, *args, ...):
必须在普通位置参数之后、普通关键字参数之前
普通关键字参数
3
def func(a, b, *args, c, ...):
必须在 *args 之后、**kwargs 之前
**kwargs(不定长)
4
def func(..., **kwargs):
必须在最后,接收所有剩余关键字参数
3. 错误案例:顺序错了会怎样?代码语言:python复制# 错误1:*args 放在普通位置参数前面
def wrong_func(*args, a, b):
print(args, a, b)
# 调用报错:TypeError: wrong_func() missing 2 required keyword-only arguments: 'a' and 'b'
# 原因:*args 把所有位置参数都收了,a 和 b 没收到值
wrong_func(1, 2)
# 错误2:**kwargs 放在普通关键字参数前面
def wrong_func2(a, **kwargs, b):
print(a, kwargs, b)
# 定义就报错:SyntaxError: invalid syntax
# 原因:**kwargs 必须在最后,不能有参数跟在后面四、进阶技巧:强制位置 / 关键字参数(用 / 和 *)有时候我们想 “限制” 参数的传递方式,比如:让某些参数必须用位置传,某些必须用关键字传。这时候就需要 / 和 * 这两个符号。
1. 用 / 强制 “前面的参数必须是位置参数”/ 是一个 “分隔符”,放在函数参数里,/前面的参数只能用位置传,不能用key=value传。
代码语言:python复制# 定义:a 和 b 在 / 前面,必须位置传;c 可以随便
def func(a, b, /, c):
print(a, b, c)
# 正确调用
func(1, 2, 3) # a=1(位置)、b=2(位置)、c=3(位置)
func(1, 2, c=3) # a=1(位置)、b=2(位置)、c=3(关键字)
# 错误调用:a 用关键字传
# func(a=1, 2, 3) # 报错:SyntaxError: positional argument follows keyword argument
# 错误调用:b 用关键字传
# func(1, b=2, 3) # 同样报错2. 用 * 强制 “后面的参数必须是关键字参数”* 也是分隔符,*后面的参数只能用key=value传,不能用位置传。
代码语言:python复制# 定义:c 和 d 在 * 后面,必须关键字传;a 和 b 可以随便
def func(a, b, *, c, d):
print(a, b, c, d)
# 正确调用
func(1, 2, c=3, d=4) # a=1(位置)、b=2(位置)、c=3(关键字)、d=4(关键字)
func(a=1, b=2, c=3, d=4)# a=1(关键字)、b=2(关键字)、c=3(关键字)、d=4(关键字)
# 错误调用:c 用位置传
# func(1, 2, 3, d=4) # 报错:TypeError: func() takes 2 positional arguments but 3 were given
# 错误调用:d 用位置传
# func(1, 2, c=3, 4) # 报错:SyntaxError: positional argument follows keyword argument3. 混合使用 / 和 *(实战常用)把 和 * 结合,能精确控制参数传递方式,比如:
代码语言:python复制# 定义:a必须位置传,d必须关键字传,b和c随便
def func(a, /, b, c, *, d):
print(a, b, c, d)
# 正确调用
func(1, 2, 3, d=4) # a=1(位置)、b=2(位置)、c=3(位置)、d=4(关键字)
func(1, b=2, c=3, d=4) # a=1(位置)、b=2(关键字)、c=3(关键字)、d=4(关键字)
# 错误调用
# func(a=1, 2, 3, d=4) # a用关键字传,报错
# func(1, 2, 3, 4) # d用位置传,报错这种写法在 Python 内置函数里很常见,比如 sorted() 函数:sorted(iterable, /, *, key=None, reverse=False),iterable 必须位置传,key 和 reverse 必须关键字传。
五、实战案例:*args 和 **kwargs 结合用光看基础没用,咱们搞两个实际场景,看看怎么灵活搭配这俩参数。
案例 1:多语言批量问候函数需求:能同时问候多个人(数量不确定),支持指定语言(中文 / 英文 / 日语)、是否加感叹号(默认加)。
代码语言:python复制def multi_greet(*names, **kwargs):
# 1. 处理默认参数:没传 lang 就用中文,没传 with_excl 就加感叹号
lang = kwargs.get("lang", "zh") # 语言:zh=中文,en=英文,jp=日语
with_excl = kwargs.get("with_excl", True) # 是否加感叹号
# 2. 根据语言选择问候语
greet_map = {
"zh": "你好",
"en": "Hello",
"jp": "こんにちは"
}
greet_msg = greet_map[lang] # 拿到对应语言的问候语
# 3. 处理感叹号
if with_excl:
greet_msg += "!"
else:
greet_msg += "。"
# 4. 批量问候每个人(*names 里的名字)
if not names: # 防止没传名字
print("请至少传入一个名字!")
return
for name in names:
print(f"{greet_msg} {name}")
# 测试1:默认中文+感叹号,问候2人
print("测试1:")
multi_greet("张三", "李四")
# 输出:
# 你好!张三
# 你好!李四
# 测试2:英文+不加感叹号,问候3人
print("n测试2:")
multi_greet("Alice", "Bob", "Charlie", lang="en", with_excl=False)
# 输出:
# Hello. Alice
# Hello. Bob
# Hello. Charlie
# 测试3:日语,问候1人,传额外参数(不影响)
print("n测试3:")
multi_greet("田中", lang="jp", city="东京") # city 没用到,但不报错
# 输出:
# こんにちは!田中案例 2:动态求和 + 结果格式化需求:能求任意多个数字的和,支持指定结果保留几位小数(默认 2 位)、是否加千分位分隔符(默认不加)。
代码语言:python复制def dynamic_sum(*nums, **kwargs):
# 1. 校验参数:必须传数字
if not nums:
raise ValueError("请至少传入一个数字!")
for num in nums:
if not isinstance(num, (int, float)):
raise TypeError(f"参数 {num} 不是数字!")
# 2. 计算总和
total = sum(nums)
# 3. 处理格式化参数
decimal = kwargs.get("decimal", 2) # 保留几位小数,默认2位
thousand_sep = kwargs.get("thousand_sep", False) # 是否加千分位
# 4. 格式化结果
# 先保留小数
formatted = round(total, decimal)
# 再处理千分位(转成字符串处理)
if thousand_sep:
formatted = f"{formatted:,}" # 用 , 分隔千分位
return formatted
# 测试1:默认保留2位小数,不加千分位
print(dynamic_sum(100, 200, 300)) # 600.0(round后是600.0,显示简化)
# 测试2:保留1位小数,加千分位
print(dynamic_sum(1234.5, 6789.1, decimal=1, thousand_sep=True)) # 8,023.6
# 测试3:传非数字参数(报错)
# print(dynamic_sum(100, "200")) # 报错:TypeError: 参数 200 不是数字!六、避坑指南:这 5 个坑千万别踩!用 *args/**kwargs 时,90% 的错误都出自这几个坑,看完再也不用 debug 到半夜。
坑 1:参数顺序搞反(最常见)症状:报 TypeError: missing required keyword-only arguments 或 SyntaxError。
原因:把 *args 放普通位置参数前面,或 **kwargs 没放最后。
解决:严格按 “普通位置参数 → *args → 普通关键字参数 → **kwargs” 顺序写。
坑 2:给 *args 传列表 / 元组时没 “解包”症状:函数里遍历 args 时报错(比如把列表当数字加)。
原因:直接把列表传给 *args,没加 *,导致整个列表成了元组的一个元素。
解决:传列表 / 元组时加 * 解包,让元素逐个进入 args。
代码语言:python复制def add(*args):
total = 0
for num in args:
total += num
return total
nums = [1, 2, 3]
# 错误:没解包,args 变成 ([1,2,3],),遍历会报 TypeError
# print(add(nums)) # 报错:unsupported operand type(s) for +=: 'int' and 'list'
# 正确:加 * 解包,args 变成 (1,2,3)
print(add(*nums)) # 6坑 3:给 **kwargs 传字典时没 “解包”症状:报 TypeError: func() takes 0 positional arguments but 1 was given。
原因:直接把字典传给 **kwargs,没加 **,导致字典被当成位置参数传了。
解决:传字典时加 ** 解包,让键值对变成关键字参数。
代码语言:python复制def print_user(**kwargs):
print(f"姓名:{kwargs['name']},年龄:{kwargs['age']}")
user = {"name": "小明", "age": 18}
# 错误:没解包,字典被当成位置参数,函数要关键字参数
# print_user(user) # 报错:takes 0 positional arguments but 1 was given
# 正确:加 ** 解包,字典变成 name="小明", age=18
print_user(**user) # 姓名:小明,年龄:18坑 4:同一参数既用位置又用关键字传症状:报 TypeError: got multiple values for argument 'xxx'。
原因:一个参数被传了两次值(比如先位置传 a=1,又关键字传 a=2)。
解决:一个参数只选一种传递方式。
代码语言:python复制def func(a, b):
print(a, b)
# 错误:a 既位置传 1,又关键字传 2
# func(1, a=2) # 报错:got multiple values for argument 'a'
# 正确:只选一种方式
func(1, 2) # 都用位置
func(a=1, b=2) # 都用关键字坑 5:混合 / 和 * 时违反传递规则症状:报 SyntaxError 或 TypeError。
原因:/ 前面的参数用了关键字传,或 * 后面的参数用了位置传。
解决:严格遵守 和 * 的规则:/ 前必位置,* 后必关键字。
七、常见问题(FAQ):新手常问的 5 个问题1. *args 和 **kwargs 的名字能改吗?能!比如 *nums、**user_info,只要前面带 * 或 ** 就行。但强烈推荐用*args和**kwargs—— 这是 Python 开发者的通用约定,别人看你代码时能一眼明白意思。
2. 函数里能只写 *args 或只写 **kwargs 吗?当然能!比如只处理位置参数就写 *args,只处理关键字参数就写 **kwargs:
代码语言:python复制# 只写 *args
def print_nums(*args):
for num in args:
print(num)
print_nums(1, 2, 3) # 输出 1、2、3
# 只写 **kwargs
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="小红", age=19) # 输出 name:小红、age:193. *args 能接收关键字参数吗?不能!*args 只负责 “位置参数”,如果传了关键字参数,会被 **kwargs 接收(如果函数里有 **kwargs);如果没有 **kwargs,就会报错:
代码语言:python复制def func(*args):
print(args)
# 错误:传了关键字参数,*args 不收,又没有 **kwargs
# func(1, 2, name="小明") # 报错:TypeError: func() got an unexpected keyword argument 'name'4. 能给 *args 和 **kwargs 加默认值吗?没必要,也不推荐。因为 *args 默认是空元组(()),**kwargs 默认是空字典({}),就算没传参数也不会报错。比如:
代码语言:python复制def func(*args, **kwargs):
print(args) # 没传参数时输出 ()
print(kwargs) # 没传参数时输出 {}
func() # 不报错,正常输出空元组和空字典5. 调用函数时,能同时传普通参数和 *args/**kwargs 吗?能!只要顺序对就行。比如:
代码语言:python复制def func(a, *args, b, **kwargs):
print("普通位置参数 a:", a)
print("*args:", args)
print("普通关键字参数 b:", b)
print("**kwargs:", kwargs)
# 调用:a=1(普通位置),args=(2,3)(位置参数),b=4(普通关键字),kwargs={"c":5,"d":6}(关键字参数)
func(1, 2, 3, b=4, c=5, d=6)运行结果:
代码语言:python复制普通位置参数 a: 1
*args: (2, 3)
普通关键字参数 b: 4
**kwargs: {'c': 5, 'd': 6}八、面试题:*args/kwargs 常考 5 题(附答案)面试时只要涉及 Python 函数参数,基本都会问这几个题,记下来稳拿分!
1. 请解释 Python 中的 *args 和 **kwargs 分别是什么,有什么区别?答案:
*args 是 “不定长位置参数”,把调用函数时传的位置参数打包成元组,只能接收位置参数;**kwargs 是 “不定长关键字参数”,把调用函数时传的关键字参数(key=value) 打包成字典,只能接收关键字参数;区别:接收的参数类型不同(位置 vs 关键字),打包后的类型不同(元组 vs 字典)。2. Python 函数参数的正确顺序是什么?如果顺序错了会怎样?答案:
正确顺序是: 普通位置参数 → *args → 普通关键字参数 → kwargs**。
顺序错了会报 SyntaxError(定义时错)或 TypeError(调用时错),比如把 *args 放普通位置参数前面,调用时会导致普通参数收不到值。
3. 如何强制函数的某些参数必须用位置传递,某些必须用关键字传递?答案:
用 和 * 两个分隔符:
在 前面的参数,必须用位置传递,不能用 key=value;在 * 后面的参数,必须用关键字传递,不能用位置传;比如 def func(a, /, b, *, c):,a 必位置,c 必关键字,b 随便。4. 当 *args 和 **kwargs 与普通参数混合使用时,容易出现哪些问题?如何避免?答案:
容易出现 3 个问题:
参数顺序错误:比如 *args 放普通位置参数前面; 避免:严格按 “普通位置 → *args → 普通关键字 → **kwargs” 写。
解包错误:给 *args 传列表没加 *,给 **kwargs 传字典没加 **; 避免:传列表 / 元组给 *args 时加 *,传字典给 **kwargs 时加 **。
参数重复传递:同一参数既用位置又用关键字传; 避免:一个参数只选一种传递方式。
5. 请写一个函数,支持动态传入多个数字,计算它们的平均值,同时支持传入关键字参数 scale 指定结果保留的小数位数(默认保留 2 位),如果没传数字则报错。答案:
先校验是否传了数字,再计算平均值,最后按 scale 保留小数:
代码语言:python复制def calc_average(*nums, scale=2):
# 1. 校验:没传数字就报错
if not nums:
raise ValueError("必须传入至少一个数字!")
# 2. 校验:所有参数必须是数字
for num in nums:
if not isinstance(num, (int, float)):
raise TypeError(f"参数 {num} 不是有效数字!")
# 3. 计算平均值
average = sum(nums) / len(nums)
# 4. 按 scale 保留小数
return round(average, scale)
# 测试
print(calc_average(1, 2, 3)) # 输出 2.0(默认保留2位)
print(calc_average(1, 3, 5, 7, scale=1)) # 输出 4.0(保留1位)
print(calc_average(2.5, 3.5, 4.5, scale=3)) # 输出 3.500(保留3位)
# calc_average() # 报错:ValueError: 必须传入至少一个数字!
# calc_average(1, "2", 3) # 报错:TypeError: 参数 2 不是有效数字!九、总结:掌握这些,函数参数再也不报错其实 *args 和 **kwargs 没那么复杂,核心就 3 点:
*args 收位置参数,打包成元组;**kwargs 收关键字参数,打包成字典;参数顺序记死:普通位置 → *args → 普通关键字 → **kwargs;解包要加 */**,强制传递用 //*。只要把这些知识点吃透,再结合案例多练几遍,以后写函数再也不用纠结 “参数不够用” 或 “传参报错” 了,代码也会更优雅、更复用!