Python 的函数参数看起来只是 def func(a, b):这种语法。真正重要的是:调用函数时,Python 会把你传入的值绑定到函数签名里的名字。理解这件事,位置参数、关键字参数、默认参数、*args、**kwargs都会变得清楚。
1. 先区分“参数”和“实参”
参数是函数定义里的名字,实参是调用函数时传进去的值。
def greet(name, message): print(f"{name}: {message}")greet("Yves", "Hello")
这里 name和 message是参数,"Yves"和 "Hello"是实参。函数执行时,Python 会得到两个局部变量:
name = "Yves"message = "Hello"
2. 位置参数:按顺序绑定
位置参数靠顺序决定谁是谁。
def power(base, exponent): return base ** exponentpower(2, 3) # 8
2绑定给 base,3绑定给 exponent。这种写法短,但当参数很多时,可读性会下降。
3. 关键字参数:按名字绑定
关键字参数靠名字决定绑定关系,顺序可以调整。
power(exponent=3, base=2) # 8
这类写法适合配置型函数:
def connect(host, port, timeout): ...connect(host="localhost", port=5432, timeout=3)
调用者一眼能看到每个值的含义。
4. 默认参数:给参数一个备用值
默认参数让调用者少传一些值。
def create_user(name, role="member", active=True): return {"name": name, "role": role, "active": active}create_user("Tom")create_user("Alice", role="admin")
默认参数有一个经典陷阱:可变对象会在函数定义时创建一次,后续调用共享同一个对象。
def add_item(item, items=[]): items.append(item) return itemsadd_item("a") # ["a"]add_item("b") # ["a", "b"]
更稳的写法是用 None做哨兵值:
def add_item(item, items=None): if items is None: items = [] items.append(item) return items
5. *args:收集多余的位置参数
*args会把多余的位置实参收集成元组。
def total(*numbers): return sum(numbers)total(1, 2, 3, 4) # 10
常见场景是包装函数:
def log_call(fn, *args): print("calling", fn.__name__) return fn(*args)log_call(max, 3, 9, 1) # 9
这里 fn(*args)是参数解包,把元组重新展开成位置参数。
6. **kwargs:收集多余的关键字参数
**kwargs会把多余的关键字实参收集成字典。
def build_profile(name, **extra): return {"name": name, **extra}build_profile("Yves", role="dev", city="Shanghai")
结果是:
{"name": "Yves", "role": "dev", "city": "Shanghai"}
它适合做扩展点,但要控制边界。公共 API 里滥用 **kwargs会让调用者不知道到底支持哪些选项。
7. 仅限关键字参数:把配置写清楚
*后面的参数只能用关键字传递。
def export(data, *, format="json", pretty=False): ...export(records, format="csv", pretty=True)
这能避免调用者写出难读的代码:
export(records, "csv", True)
配置项越多,越应该考虑关键字专用参数。
8. 仅限位置参数:用 /限制名字
/左边的参数只能按位置传递。
def divmod_like(a, b, /): return a // b, a % bdivmod_like(10, 3)
这种写法在普通业务代码中少见,但在标准库和底层 API 里有价值。它允许函数作者未来调整参数名,同时减少调用者依赖内部命名。
9. 完整顺序:一条规则记住所有类型
Python 函数参数的推荐顺序是:
def func(pos_only, /, pos_or_kw, default=0, *args, kw_only, **kwargs): ...
也可以拆开看:
位置专用参数 → 普通参数 → 默认参数 → *args → 关键字专用参数 → **kwargs
实际代码里很少需要一次写全。大多数业务函数只需要普通参数、默认参数、关键字专用参数。
10. 实战建议
写函数时可以按这个原则判断:
参数少、含义明确时,用位置参数。
参数含义容易混淆时,用关键字参数。
配置项有默认行为时,用默认参数。
包装函数或透传调用时,用 *args和 **kwargs。
公共 API 的配置项,优先做成关键字专用参数。
可变默认值用 None初始化,减少共享状态风险。
11. 一个综合例子
def request( method, url, /, *, timeout=5, headers=None, retries=0, **options,): headers = headers or {} print(method, url, timeout, headers, retries, options)request( "GET", "https://example.com", timeout=10, headers={"Accept": "application/json"}, retries=2, verify_ssl=True,)
这个签名表达了清晰的约束:
method和 url是核心输入,按位置传。
timeout、headers、retries是配置,必须写名字。
额外选项进入 options,给未来扩展留空间。
总结
Python 函数参数的核心问题只有一个:调用时的值如何绑定到函数内部的名字。位置参数解决顺序,关键字参数解决可读性,默认参数解决常用值,*args和 **kwargs解决弹性,/和 *解决 API 边界。
把这些工具组合好,函数签名本身就会变成文档。调用者少猜,维护者少解释。