2.15. 参数

一个函数可以通过多种方式调用,其形参也可以用多种形式声明。这些组合乍看之下令人望而生畏;但实际上三四种模式就涵盖了几乎所有情况。

2.15.1. 位置参数与关键字参数

最简单的调用是按位置传递实参——第一个值传给第一个形参,第二个传给第二个,依此类推:

def rect(x, y, w, h):
    return (x, y, w, h)

rect(10, 20, 100, 50)

同样的调用也可以按关键字传递实参,显式命名每个形参:

rect(x=10, y=20, w=100, h=50)

关键字参数与顺序无关,并使调用具有自描述性,代价是需要输入更多内容。位置参数和关键字参数可以在一次调用中混用,但每个位置参数都必须出现在任何关键字参数之前:

rect(10, 20, w=100, h=50)         # OK
rect(x=10, 20, 100, 50)           # SyntaxError

2.15.2. 默认值

形参可以声明一个默认值,在调用者未提供时使用:

def greet(name, greeting="hello"):
    print(greeting, name)

greet("Alice")                    # hello Alice
greet("Alice", "hi")              # hi Alice
greet("Alice", greeting="hey")    # hey Alice

def 行中,带默认值的形参必须位于不带默认值的形参之后

警告

默认值只在 def 运行时求值一次——而不是每次调用时。使用可变默认值([]{})会导致同一个对象被所有采用该默认值的调用共享。应改用 None 作为哨兵值:

def append_to(item, target=None):
    if target is None:
        target = []
    target.append(item)
    return target

2.15.3. 可变长度:*args**kwargs

* 为前缀的形参会将所有剩余的位置参数收集到一个 tuple 中。以 ** 为前缀的形参会将所有剩余的关键字参数收集到一个 dict 中。惯用的名称是 argskwargs,但任何标识符都可以:

def report(label, *values, **options):
    print(label, values, options)

report("temps", 21, 22, 23, unit="C", precision=1)

输出:

temps (21, 22, 23) {'unit': 'C', 'precision': 1}

函数很少同时需要两者。最常见的用法是将参数从包装函数转发给内部调用:

def log_and_call(func, *args, **kwargs):
    print("calling", func.__name__)
    return func(*args, **kwargs)

调用处的镜像语法可以将一个可迭代对象解包为位置参数(*),或将一个 dict 解包为关键字参数(**):

point = (10, 20, 100, 50)
rect(*point)                      # same as rect(10, 20, 100, 50)

kwargs = {"x": 10, "y": 20, "w": 100, "h": 50}
rect(**kwargs)                    # same as rect(x=10, y=20, ...)

2.15.4. 仅关键字形参

在形参列表中单独出现的 *(不附加在名称上)会将其后的每个形参标记为仅关键字——调用者必须使用名称:

def crop(buffer, *, x, y, w, h):
    ...

crop(buffer, x=0, y=0, w=100, h=100)   # OK
crop(buffer, 0, 0, 100, 100)           # TypeError

对于布尔标志以及其他在调用处用裸值无法做到自解释的参数,可以使用这种方式。