1、关键字参数(positional argument)和位置参数(keyword argument)

Python函数的参数根据函数在调用时(注意,不是函数定义时)传参的形式分为关键字参数和位置参数。

(1)关键字参数:

关键字参数是指在函数调用传参时,由标识符(如name=)引导的参数,或者放在一个由**引导的字典里进行传递。如下所示:

complex(real=3, imag=5)
complex(**{'real': 3, 'imag': 5})

(2)位置参数

不是关键字参数的参数就是位置参数。它除了单独传递之外,还可以放在一个由*引导的可迭代序列(列表、元组等)里进行传递。如下所示:

complex(3, 5)
complex(*(3, 5))

位置参数总是放在函数的参数列表最前方,关键字参数必须要放在位置参数后面。它们之间的位置关系如下所示:

def func(arg1, arg2, kwarg1, kwarg2):
func(1, 2, kwarg1=3, kwarg2=4)

这里arg1、arg2是位置参数,kwarg1,kwarg2是关键字参数。关键字参数的key(也就是这里的'kwarg1=3'中的'kwarg1','kwarg2=4'中的'kwarg2')要保证和形参名称一致

2、接受任意数量的参数

(1)接受任意数量的位置参数

"*"号表达式除了上一章我们讲的用于对任意长度可迭代对象进行拆分之外, 还能在函数定义中使用,用于定义一个可以接受任意数量位置参数的函数,如下所示:

def avg(first, *rest):
    print(rest)
    return (first + sum(rest)) / (1 + len(rest))
print(avg(1, 2, 3, 4, 5))
# (2, 3, 4, 5)
# 1. 5

"*"开头的参数必须做为最后一个位置参数使用,且"*"开头的参数传进来后是元组数据结构。

(2)接受任意数量的关键字参数

想接受任意数量的关键字参数,我们可以类似地使用"**"开头的参数。如下所示:

import html
def make_element(name, value, **attrs) -> str:
    key_values = [ ' %s="%s"' % item for item in attrs.items()]
    attr_str = ''.join(key_values)
    # Perform a string formatting operation.
    element = '<{name} {attrs}>{value}</{name}>'.format(name=name, attrs=attr_str, value=html.escape(value))
    return  element

res_1 = make_element('item', 'Albatross', size='large', quantity=6)
res_2 = make_element('p', '<spam>') # escape会把这里'<spam>'中的'<'和'>'替代成安全的序列&lt; &gt;
print(res_1) # <item  size="large" quantity="6">Albatross</item>
print(res_2) # <p >&lt;spam&gt;</p>

"**"开头的参数必须做为最后一个关键字参数使用,且"**"开头的参数传进来后是字典数据结构。

(3)同时接受任意数量的位置参数和关键字参数

如果想要函数同时接受任意数量的位置参数和关键字参数,只要联合使用"*"和"**"即可。

def anyargs(*args:tuple, **kwargs:dict):
    print(args)
    print(kwargs)
anyargs(2, 3, 4, 5, time=1, data=2)
# (2, 3, 4, 5)
# {'time': 1, 'data': 2}

3、keyword-only参数

前面说过,"*"打头的参数只能做为最后一个位置参数,"**"打头的参数只能做为最后一个关键字参数(自然也是最后一个参数),而依此推断"*"打头的参数后的参数就必然是关键字参数了。

# 出现在*args之后的参数称为keyword-only参数
# 这两个例子中y都只能是关键字参数,在传参时要保证key和形参的一致性
def a(x, *args, y):
    print(y)
def b(x, *args, y, **kwargs):
    print(y)

a(4, 6, 7, 8, y=1)
b(4, 6, 7, 3, y=1, data=2, year=3)

# 1
# 1

这样的参数称为keyword-only参数,即出现在*args之后的参数只能做为关键字参数使用。
我们可以充分利用这一性质,将关键字参数放在以*打头的参数后,或者一个单独的*之后,强迫函数的调用者必须传关键字参数,比如下面这样:

def recv(max_size, *, block):
    'Receives a message'
    pass

recv(1024, True)  # recv2() takes 1 positional argument but 2 were given
# and missing 1 required keyword-only argument: 'block'
recv(1024, block=True) # OK

这项技术在实际项目中,可以用来为接受任意数量的位置参数的函数来指定关键字参数,比如下面这个带截断功能的求最小值函数。这里的clip参数被强迫为必须按照关键字参数传入,而且设定了一个默认值None, 使参数为可选的。如下所示:

def mininum(*values, clip=None):
    m = min(values)
    if clip is not None:
        m = clip if clip > m else m
    return m

res1 = mininum(1, 5, 2, -5, 10)
res2 = mininum(1, 5, 2, -4, 10, clip=0)
print(res1, res2) # -5, 0

除此之外,keyword-only参数可以提高代码可读性,像下面这种函数写法:

msg = recv(1024, False)

如果代码的阅读者不熟悉recv函数的工作方式,那么可能不太明白这里的False参数有什么作用,如果这个函数的调用可以写成下面这样的话,那就清晰多了(当然,需要这个函数的编写者最开始就强制函数的使用者这样写):

msg = recv(1024, block=False)

最后,如果函数定义的的时候强制使用了keyword-only参数,那么当用户请求帮助信息时,参数信息可以很自然地显现出来:

print(help(recv))
# Help on function recv in module __main__:

# recv(max_size, *_, block)
#     Receives a message

3、可选参数(带默认值的参数)

要想定义一个可选参数,需要在函数定义中为参数赋值,并保证默认参数出现在参数列表最后。像下面这样:

def spam(a, b=42):
    print(a, b)
spam(1) # 1, 42
spam(1, 2) # 1, 2

如果默认值是可变容器,比如说列表、集合、字典等,需要把None做为默认值:如下所示:

def spam(a, b=None):
    if b is None:
        b = []

警示1: 千万不能直接像下面这样写:

def spam(a, b=[]):

如果像上面那样写,那么就会发生一些你所不期望看到的现象:如果默认值在函数体之外被修改了,那么这种修改在之后的函数调用中仍然阴魂不散,如下面所示:

def spam(a, b=[]):
    print(b)
    return b
x = spam(1)
x.append('oh!')
x.append('no!')
print(x)
spam(1)
# []
# ['oh!', 'no!']
# ['oh!', 'no!']

警示2: 在函数体中,我们常常需要判断参数是否为None,此处需要使用is运算符,千万不能直接像下面这样写:

def spam(a, b=None):
    if not b:
        b = []

这里的问题在于:尽管None会被判定为False,可还有其他许多对象(比如长度为0的字符串、列表、元组、字典等)也存在这样的行为。这样,有很多其他的特定输入也会被判定为False,然后本该是用户传进来的值直接被默认的[]覆盖掉了。如下所示:

def spam(a, b=None):
    if not b:
        b = []
spam(1) # OK
x = []
spam(1, x) # Oops! x will be overwritten by default []
spam(1, 0) # Oops! 0 will be overwritten by default []
spam(1, '') # Oops! '' will be overwritten by default []

最后,我们再来讨论一个非常棘手的问题。我们想要在函数中检测调用者是否对可选参数提供了某个特定值(可以是任意值,None也算)这样,我们自然就不能用None,0, False当做默认值然后再来做检测了,因为用户本身就可能拿它们当做参数。
要解决这个问题,可以像下面这样写:

_no_value = object()
def spam(a, b=_no_value):
    if b == _no_value:
        print("No b value supplied")
        return
    print(a, b)
spam(1) # No b value supplied
spam(1, 2) # 1 2
spam(1, None) # 1 None

这里_no_value是基于object()类创建的一个独有对象,可以用这个来对用户提供的参数做检测,因为用户几乎不可能把_no_value对象做为参数输入(除非用户传入的是相同的对象,否则哪怕是object类型的另一个对象都和_no_value对象是不同的)。
这里简要说明一下object类,object是Python中几乎所有对象的基类,object对象没有任何数据(底层缺少__dict__字典,甚至没办法设置任何属性),它唯一的作用就是用来检测相等性。

参考文献

  • [1] https://www.python.org/
  • [2] Martelli A, Ravenscroft A, Ascher D. Python cookbook[M]. " O'Reilly Media, Inc.", 2005.
10-13 19:48