上一篇 函数定义与调用 我们讲了函数的基本形态,但参数才是函数的”接口”,也是 Python 最灵活的地方。Python 函数参数系统能玩出比 C/Java 多得多的花样:默认值、可变参数、关键字参数、位置参数、*/、* 特殊分隔符……这一篇要把它们全串起来。学完之后你就能读懂 def open(file, mode='r', buffering=-1, encoding=None, ...) 这种复杂签名了。
一、位置参数
最基本的形式:按位置传参。
1 | def add(a, b): |
二、关键字参数
调用时可以显式写出参数名,顺序可以变:
1 | def greet(name, greeting): |
关键字参数一定要在位置参数之后:
1 | greet("咖飞", greeting="你好") # ✅ |
三、默认参数
给参数一个默认值,调用时可以省略:
1 | def greet(name, greeting="你好"): |
默认参数一定要放在后面:
1 | def bad(x=1, y): # ❌ SyntaxError: 非默认参数在默认参数后 |
⚠️ 可变默认参数陷阱(Python 最经典的坑)
1 | def append(item, lst=[]): |
原因:默认值 [] 只在 def 语句执行时求值一次,之后所有调用共享同一个对象。
修复:
1 | def append(item, lst=None): |
规则:默认参数值只用不可变对象(数字、字符串、元组、None)。
四、可变位置参数 *args
用 *args 接受任意多个位置参数,打包成元组:
1 | def total(*args): |
名字 args 只是惯例,也可以叫 *nums、*values。关键是那个 *。
五、可变关键字参数 **kwargs
用 **kwargs 接受任意多个关键字参数,打包成字典:
1 | def show(**kwargs): |
六、混合使用
*args 和 **kwargs 可以同时用,顺序固定:
1 | def f(位置参数, *args, **kwargs): |
例子:
1 | def log(level, *args, **kwargs): |
七、参数解包(调用时的反向操作)
在调用函数时,* 和 ** 用来”解包”:
1 | def f(a, b, c): |
这在函数转发里非常有用:
1 | def wrapper(*args, **kwargs): |
八、仅位置参数与仅关键字参数(Python 3.8+)
现代 Python 提供两个特殊分隔符:
/:它之前的参数只能按位置传;*:它之后的参数只能按关键字传。
1 | def f(pos_only, /, both, *, kw_only): |
什么时候用 /?
- 参数名不稳定:不想让调用方依赖参数名字,将来改名不影响;
- 性能极致:跳过关键字查找(微小优化);
- API 语义:像内置
abs(x)、len(s),这种”名字无关”的参数。
什么时候用 *?
- 参数很多,容易搞错顺序——强制关键字,代码可读性大幅提升;
- 布尔开关参数:
sort(items, *, reverse=False),写起来sort(items, reverse=True)比sort(items, True)清晰得多。
九、参数顺序的完整规则
一个函数的参数顺序可以有五类,按顺序:
1 | def f( |
一个”极限例子”:
1 | def spec(a, b, /, c, d, *args, e, f=10, **kwargs): |
a, b:仅位置;c, d:位置或关键字;args:可变位置;e:仅关键字,无默认;f:仅关键字,有默认;kwargs:可变关键字。
日常写函数不用同时用全部五种,但你应该看得懂别人写的这种签名。
十、类型注解与参数
参数一起写类型注解:
1 | def register( |
十一、一个”综合参数”例子
1 | # params.py |
十二、函数转发的经典模式
*args + **kwargs 让”包装函数”变得优雅:
1 | import time |
装饰器我们在 装饰器入门 详细讲。
十三、命令行参数解析预告
大多数命令行工具都用 argparse(标准库)解析参数:
1 | import argparse |
具体用法后面讲。
十四、参数传递的机制:值 or 引用?
Python 的参数传递是 “传对象引用”(pass by object reference):函数拿到的是”和外部同一个对象的引用”。
1 | def f(x): |
规则:
- 重新赋值 → 不影响外部;
- 就地修改可变对象 → 影响外部。
想让函数”完全不影响外部”,传入之前拷贝:g(b[:]) 或者内部拷贝 lst = lst.copy()。
十五、常见陷阱
- 可变默认参数:老生常谈,永远用 None + 内部判断。
*args与**kwargs顺序:args一定在kwargs前。f(*args)里 args 必须可迭代:f(*None)报错。**kwargs里 kwargs 必须是字典且 key 是字符串。- 同一个参数名多次传值:
f(1, x=1)若x是第一个参数,会 TypeError。 - 默认值参数在 def 时求值,不是调用时:
1 | import datetime |
正确:def log(msg, ts=None): if ts is None: ts = datetime.datetime.now()。
- 对 dict 用
**解包时 key 冲突:
1 | f(**{"a": 1}, a=2) # ❌ TypeError |
十六、小结与延伸阅读
- 位置参数、关键字参数、默认参数、
*args、**kwargs、/、*七种参数形式; - 参数顺序:位置 →
/→ 位置或关键字 →*args→ 仅关键字 →**kwargs; - 可变默认参数是经典陷阱;
*args/**kwargs是包装/转发函数的核心工具;- Python 参数传递是”传对象引用”;
- 学会用
/和*让 API 更严谨。
延伸阅读:
- 官方参数文档:https://docs.python.org/zh-cn/3/tutorial/controlflow.html#more-on-defining-functions
- PEP 3102 keyword-only:https://peps.python.org/pep-3102/
- PEP 570 positional-only:https://peps.python.org/pep-0570/
下一篇 作用域与闭包 我们讲变量的”能见度”问题,以及闭包是怎么”记住外层变量”的。