现代 Python 的参数系统远不止"传参调用"这么简单------仅位置形参、仅关键字形参、
*args与**kwargs的拆包魔法、默认值的绑定时机......这些特性相互交织,让很多人学完后依然一头雾水。本文从实参和形参两个视角,系统拆解 Python 函数参数的传递规则、组合约束与底层绑定逻辑,帮你彻底告别"参数混乱",写出更清晰、更安全的函数签名。
参数概念
| 术语 | 英文 | 含义 | 出现的侧 |
|---|---|---|---|
| 实参 | argument | 函数调用时实际传入的值 | 调用侧 |
| 形参 | parameter | 函数定义时声明的变量名 | 定义侧 |
python
def func(a, b, c): # a, b, c 是 parameter(形参)
...
func(1, 2, 3) # 1, 2, 3 是 argument(实参)
传递实参(argument)
- 位置实参
- 关键字实参
- [可迭代 Iterable 对象拆包](#可迭代 Iterable 对象拆包 "#%E5%8F%AF%E8%BF%AD%E4%BB%A3-iterable-%E5%AF%B9%E8%B1%A1%E6%8B%86%E5%8C%85")
- 字典拆包
位置实参
ℹ️ 信息
把实际参数传递给函数最常见的方式。
python
def func(a, b, c):
print(f"{a=} {b=} {c=}")
func(1, 2, 3)
关键字实参
💡 提示
使用关键字参数时,实际参数的顺序并不需要与函数定义中形式参数的顺序匹配。
所以我们可以不用记住函数定义中形式参数的顺序。
python
def func(a, b, c):
print(f"{a=} {b=} {c=}")
func(c=3, b=2, a=1)
位置实参与关键字实参的混用顺序
⚠️ 警告
位置参数必须出现在所有关键字参数的前面。
python
def func(a, b, c):
print(f"{a=} {b=} {c=}")
func(c=3, b=2, 1)
"""报错
func(c=3, b=2, 1) # ❌ SyntaxError: positional argument follows keyword argument
^
SyntaxError: positional argument follows keyword argument
"""
正确版本:
python
def func(a, b, c):
print(f"{a=} {b=} {c=}")
func(1, c=3, b=2)
可迭代 Iterable 对象拆包
📝 笔记
把一个可迭代对象的元素作为位置参数传递给一个函数。
python
def func(a, b, c):
print(f"{a=} {b=} {c=}")
values = ("深圳", "图书馆", "北馆")
func(*values)
💡 提示
这里的
*相当于是一种展开语法。
- 函数调用中的
*是"展开运算符",主动把一个可迭代对象打散成多个实参(使用*时,Python 调用的是可迭代对象的迭代器协议) - 赋值中的
*是"贪婪捕获符",标记一个变量去收集未被其他变量匹配的元素
python
values = ("深圳", "图书馆", "北馆")
# 贪婪捕获
a, *rest = values
print(f"{a=} {rest=}") # a='深圳' rest=['图书馆', '北馆']
# 展开运算符
print(['我', '爱', *values]) # ['我', '爱', '深圳', '图书馆', '北馆']
❌ 错误
可迭代对象的元素个数与函数形参的个数必须一样,否则会报错。
python
# TypeError: func() missing 1 required positional argument: 'c'
values = ("深圳", "图书馆")
func(*values)
# TypeError: func() takes 3 positional arguments but 4 were given
values = ("深圳", "图书馆", "北馆", "学习Python")
func(*values)
字典拆包
📝 笔记
**拆包操作符在函数调用中专门用于映射类型 。它将字典的键值对拆解为关键字实参 ,要求字典的键必须是字符串且与形参名匹配。
字典的键 "a"、"b"、"c" 变成了关键字参数名,对应的值变成了实参值,恰好与形参名 a、b、c 一一匹配,所以输出 a='深圳' b='图书馆' c='北馆'
python
def func(a, b, c):
print(f"{a=} {b=} {c=}")
values = {
"c": "北馆",
"b": "图书馆",
"a": "深圳",
}
# 等价于 func(a="深圳", b="图书馆", c="北馆")
func(**values)
实参混合 ⭐
📝 核心理解
- 位置参数(这里不包括
*expr)必须出现在所有关键字参数(包括**expr)的前面- 延迟占位
实参必须按照以下顺序进行传递:
- 首先是位置参数:包括普通的位置参数和可迭代对象拆包
- 接着是关键字参数,它可以与可迭代对象混合
- 最后是字典拆包:它可以与关键字参数混合
python
def func(a, b, c, d, e, f):
print(f"{a=} {b=} {c=} {d=} {e=} {f=}")
# 普通位置参数与可迭代对象拆包混合
func(*(1, 2), 3, 5, *[4], **{'f': 6})
# 关键字参数与可迭代对象拆包混合
func(f=6, *[1, 2, 3, 4, 5])
func(1, e=4, *(2, 3), *(5,), **{'f': 6})
func(*(1, 2), e=5, *[3, 4], f=6)
# 字典拆包与关键字参数混合
func(1, **{'c': 3, 'e': 5}, d=3, **{'f': 6}, b=2, )
func(1, **{'b': 2, 'c': 3}, d=4, **{'e': 5, 'f': 6})
📋 摘要:延迟占位(Delayed Slot Occupation)
Python 在函数调用时,参数绑定遵循分阶段执行 的顺序,导致某些"写在后面"的实参反而先占坑。
绑定顺序(不可改变)
位置实参先跑
所有裸露位置实参 + 所有
*expr展开的元素,按出现顺序拼成一条队列,依次占据形参槽位。关键字实参后跑
所有裸露关键字实参 +
**expr展开的键值对,在位置实参全部就位后,再去匹配剩余空槽。核心矛盾
bash书写顺序 ≠ 绑定顺序 关键字在前 ≠ 关键字先占坑 *expr 在后 ≠ *expr 后占坑即使你在调用时把
d=4写在*(5,)前面,*(5,)展开的5也会先于d=4去占据形参d的槽位,导致关键字实参后处理时发现冲突。
pythondef func(a, b, c, d, e, f): print(f"{a=} {b=} {c=} {d=} {e=} {f=}") # ❌ TypeError: func() got multiple values for argument 'd' func(1, *(2, 3), d=4, *(5,), **{'f': 6})安全原则
*expr放在所有关键字实参之前 → 不会产生延迟占位*expr放在关键字实参之后 → 需确保展开的元素数量不会越界到已声明关键字的形参位置
定义形参(parameter)
位置和关键字形参(默认形参)
ℹ️ 信息
- 使用方实参角度:同时允许位置实参和关键字实参
- 定义方形参角度:分为必须形参(无默认值)和可选形参(有默认值)又叫关键字参数,且必须形参必须排在可选形参之前
必须的形参与可选的形参差别:
python
def func_2(a=None):
"""a 是可选的------调用时可以不传"""
if a is not None:
"""do something"""
...
def func_3(a):
"""a 是必传的------调用时必须提供"""
pass
func_2()
func_3(2)
默认形参
python
def func(a, b=2, c=3):
print(f"{a=} {b=} {c=}")
func(1)
⚠️ 警告:Python 硬性语法规则
有默认值的形参(可选形参)必须出现在所有无默认值的形参(必须形参)之后。
python
# ❌ 语法错误:可选形参在前,必须形参在后
def func(b=2, a, c=3):
... # ↑ a 没有默认值,但夹在可选形参中间
# SyntaxError: parameter without a default follows parameter with a default
这条规则的核心原因在于位置实参的绑定方式------位置实参是按顺序"填坑"的:
python
def func(b=2, c=3, a): # 假设这是合法的
...
# 调用时只传一个位置实参:
func(1)
# 问题:这个 1 应该给 b,还是跳过 b、c 直接给 a?
#
# 如果 1 给 b → a 没有值 → 报错
# 如果 1 跳过去给 a → b 和 c 已经是可选的了,Python 没法判断你的意图
Python 没法判断你的意图
可变的默认值
🚨 危险:可变默认值陷阱
默认值在函数定义时 只计算一次,之后每次调用复用同一个对象 。 当默认值为列表、字典或类实例等可变对象时,多次调用会累积副作用。
python
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
执行结果:
截图描述:三次调用分别输出
[1]、[1, 2]、[1, 2, 3],说明每次调用都在修改同一个列表对象。

可变数量的位置形参
📝 笔记
*args的核心语义是:贪婪吸收所有剩余的位置实参。
截图描述:示意图展示*args将形参列表"切割"为前后两部分,*args之前的位置形参按顺序绑定传入的位置实参,*args贪婪吸收所有剩余位置实参,*args之后的形参自动变为仅关键字形参。

python
def concat(city="深圳", *args, sep="/"):
print(f"{type(args) = } {args=}")
return sep.join((city,) + args)
print(concat("北京", "上海", "广州", sep=" | "))
print(concat())
输出:
python
type(args) = <class 'tuple'> args=('上海', '广州')
北京 | 上海 | 广州
type(args) = <class 'tuple'> args=()
深圳
可变数量的关键字形参
📝 笔记
**kwargs的核心语义是:贪婪吸收所有未匹配的关键字实参,打包成一个字典。
*args 贪婪吸收未匹配的位置实参,**kwargs 贪婪吸收所有未匹配的关键字实参,然后无透明传递 给 query_data 函数。
python
def query_data(table, limit=0, offset=20):
print(f"select from {table} limit {limit, offset}")
def wrapper(*args, **kwargs):
print(f"{args=} {kwargs=}")
query_data(*args, **kwargs) # 无透明传递
print("-"*45)
wrapper("user", limit=10, offset=20)
wrapper("user", 10, 20)
wrapper(**{"table": "user", "limit": 10, "offset": 20})
输出:
sql
args=('user',) kwargs={'limit': 10, 'offset': 20}
select from user limit (10, 20)
---------------------------------------------
args=('user', 10, 20) kwargs={}
select from user limit (10, 20)
---------------------------------------------
args=() kwargs={'table': 'user', 'limit': 10, 'offset': 20}
select from user limit (10, 20)
---------------------------------------------
仅位置形参
📝 笔记
/分隔符强制其左边的形参只能通过位置传递,不能用关键字指定 。/左边的参数剥夺了调用者使用关键字传参的权利。
python
def func(pos1, pos2, /, pos_or_kw1, pos_or_kw2, *, kw1, kw2):
"""
pos1, pos2 → 仅位置(/ 左边)
pos_or_kw1, pos_or_kw2 → 位置或关键字(/ 和 * 之间)
kw1, kw2 → 仅关键字(* 右边)
"""
pass
# 合法调用
func(1, 2, 3, 4, kw1=5, kw2=6) # ✅
func(1, 2, 3, pos_or_kw2=4, kw1=5, kw2=6) # ✅
# 非法调用
func(pos1=1, pos2=2, ...) # ❌ TypeError: got positional-only arguments as keyword
python
print(func.__code__.co_posonlyargcount) # 2
print(func.__code__.co_kwonlyargcount) # 2
print(func.__code__.co_varnames) # ('pos1', 'pos2', 'pos_or_kw1', 'pos_or_kw2', 'kw1', 'kw2')
截图描述:流程图展示了仅位置形参的参数绑定检查逻辑。

⚠️ 警告
注意,仅位置形参也是可选的。
python
def func(a, b=2, /):
print(f"{a=} {b=}")
func(1)
func(1, 5)
🚨 危险
但是还是遵守必须形参在前,可选形参在后。
python
# ✅ 合法:必须形参在前,可选形参在后
def func(a, b, /, c, d=4):
pass
# ❌ 非法:可选形参在前,必须形参在后(即使跨 / 也不允许)
def func(a, b=2, /, c):
pass
# SyntaxError: parameter without a default follows parameter with a default
📝 笔记
参数名可能在未来改变。库作者想保留重命名参数的权利,而不破坏用户代码。
python
# 需求:库函数要求用户"按顺序提供,不要依赖参数名"
def configure_connection(
host: str,
port: int,
timeout: float,
/, # ← 这些参数名是内部细节
*,
pool_size: int = 10, # ← 这些参数名是公开 API
retry_attempts: int = 3,
) -> ConnectionPool:
"""
建立连接池。
host, port, timeout 按位置提供------顺序即语义。
pool_size, retry_attempts 按关键字提供------名称即文档。
"""
...
# 使用:自文档化且安全
pool = configure_connection("db.internal", 5432, 5.0, pool_size=20)
同名的关键字实参
📝 笔记
仅位置形参的名字不会"占用"关键字命名空间。
注意下面的代码关键字传递实参 name="深圳图书馆",并没有影响前面位置参数,也不会像之前报错。
python
def func_name(name, /, **kwargs):
print(f"{name=} \n{kwargs=}")
# 注意这里关键字传递实参 name="深圳图书馆",并没有影响前面位置参数
func_name("Pkmer", name="深圳图书馆")
"""输出
name='Pkmer'
kwargs={'name': '深圳图书馆'}
"""
截图描述:流程图展示了仅位置形参与关键字实参的绑定流程。

仅关键字形参
📝 笔记
两种方式:
- 可变数量的位置形参的后面
- 单独的
*后面
python
def kwonly1(*args, c):
print(f"{args=} {c=}")
kwonly1(1, 2, c=3)
kwonly1(c=3)
"""输出
args=(1, 2) c=3
args=() c=3
"""
python
def kwonly2(a, b=42, *, c=3):
print(f"{a=} {b=} {c=}")
kwonly2(1, 2, c=3)
kwonly2(1, c=3)
"""输出
a=1 b=2 c=3
a=1 b=42 c=3
"""
形参的组合规则
❗ 重要
- 仅位置形参首先出现,然后是一个
/- 常规的位置形参出现在仅位置形参的后面
- 可变数量的位置形参出现在常规的位置形参的后面
- 仅关键字形参出现在可变数量的位置形参的后面
- 可变数量的关键字形参总是出现在最后
- 对于仅位置形参和常规的位置形参,所有必须的形参都是在所有默认形参的前面。
python
def all_params(a, /, b, c=28, *args, d=256, e, **kwargs):
print(f"{a=} {b=} {c=} {args=} {d=} {e=} {kwargs=}")
all_params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, e=11, f=12, g=13)
"""输出
a=1 b=2 c=3 args=(4, 5, 6, 7, 8, 9, 10) d=256 e=11 kwargs={'f': 12, 'g': 13}
"""