Python 的函数参数详解

Python 的参数很复杂,有命名、非命名、*args**kwargs 等等,很容易混淆。记下来防止以后忘记。

参数类型

其实文档里是有这个的,在 inspect.Parameter 里。从定义的角度看:

Name Meaning
POSITIONAL_ONLY 只能根据位置来赋值(Python 里无法定义这样的参数,但是很多内置函数是这样的)
POSITIONAL_OR_KEYWORD 可以根据位置来赋值,也可以通过 keyword 赋值(这是 Python 里函数的标准行为方式)
VAR_POSITIONAL 一个 tuple 表示多个参数,即 *args
KEYWORD_ONLY 只能通过 keyword 赋值(指的是定义在 *args 之后的参数——*args 会取走所有 unnamed 参数,故不能根据位置获得参数值)
VAR_KEYWORD 一个 dict 表示多个 named 参数,即 **kwargs

注:用 inspect.signature 可以检视函数各个参数的详细信息。

调用的角度看,函数分为:

  • named 表示命名参数
  • unnamed 表示非命名参数

但是实际中是有带***的东西出现的,接着看。

调用者传入参数

对于参数顺序,Python 本身有以下限制:

  • ** 参数必须位于一般参数和 * 参数之后,即只能放最后(但是可以不止一个)
  • named 参数必须在 unnamed 参数之后

这里有个 bug (or feature):带 * 参数可以放在 named 之后。这种情况下仍然是先 positional 地匹配——等价于把带 * 参数移到前面。

根据 Python 语言规范,unamed 参数必须写在 named 参数之前。

传参过程是这样的:

  • * 的参数被展开(unpack)成若干个 unnamed 参数
  • ** 的参数展开成若干个命名 named 参数
  • 其他的,直接传,不用动

注意,以上过程都是 in place 的,即相对左右其他参数的位置保持不变。

*** 只是一个语法糖,它做的事情很简单,就是碰到的时候立马展开,变成一排 unnamed 或 named 参数。怎么样,简单吧!

被调用者接收参数

Python 对形参表有以下限制:

  • *args**kwargs 只能出现一次
  • **kwargs 参数必须放在最后

谨慎使用 KEYWORD_ONLY 的参数!它的定义方式非常 implicit。

接受函数的过程是这样的:

  • 一般参数,即 POSITIONAL_OR_KEYWORD 参数,直接拿到当前位置的(positional)unamed 值,无论该参数是否有 default
  • VAR_POSITIONAL 参数拿走所有剩下的 unamed 参数
  • KEYWORD_ONLY 从所有的 named 参数中匹配,如果匹配不到又没有默认值,则报错
  • VAR_KEYWORD 拿到所有剩下的 named 参数,包括没被POSITIONAL_OR_KEYWORD 拿走的和没被 KEYWORD_ONLY 拿走的

每个参数不能被赋值两次(即使值一样),没有 default 的函数必须被赋值。否则,报 TypeError。

总结

可以把参数看成两部分:一个 unnamed 参数组成的序列(tuple)和一个 named 参数组成的 dict。

这也解释了为什么

1
2
def forward(*args, **kwargs):
fn(*args, **kwargs)

能实现转发。

tuple 按位置匹配,dict 按 keyword 匹配,不能漏,不能重复。