与其他编程语言样,Pyhton 允许在调用函数时,按照位置传递参数。
def remainder(number, division):
return number % division
assert remainder(20, 7) == 6
Python 函数里面的所有普通参数,除了按照位置传递外,还可以按照关键字传递。调用函数时,在调用括号内可以把关键字的名称都写到 = 左边,把参数值写在右边。这种写法不在乎参数的顺序,只要把必须指定的所有位置参数全都传递过去即可。另外,关键字形式与位置形式也可以混用。下面四种写法的效果相同:
remainder(20, 7)
remainder(20, division=7)
remainder(number=20, division=7)
remainder(division=7, number=20)
如果混用,那么位置参数必须出现在关键字参数之前,否则就会出错。
remainder(number=20, 7)
>>>
Traceback ...
SyntaxError:positional argument follows keyword argument
每个参数只能指定一次,不能既通过位置形式指定,又通过关键字形式指定。
remainder(20, number=7)
>>>
Traceback ...
TypeError:reamainder() got multiple values for argument
如果有一份字典,而且字典里面的内容能够用来调用 remainder 这样的函数,那么可以把 ** 运算符加在字典前面,这会让 Python 把字典里面的键值以关键字参数的形式传给函数。
my_kwargs = {
'number':20,
'division':7,
}
assert remainder(**my_kwargs) == 6
调用函数时,带 ** 操作符的参数可以和位置参数或关键字参数混用,只要不重复指定就行。
my_kwargs = {
'division':7,
}
assert remainder(number=20, **my_kwargs) == 6
也可以对多个字典分别施加 ** 操作,只要这些字典提供的参数不重叠就好。
my_kwargs = {
'number': 20,
}
other_kwargs = {
'division': 7,
}
assert remainder(**my_kwargs, **other_kwargs) == 6
定义函数时,如果想让这个函数接收任意数量的关键字参数,那么可以在参数列表里写上外能形参 **kwargs, 它会把调用者传进来的参数收集合在一个字典里面稍后处理(第26条 讲了一种特别适合这么做的情况)。
def print_parameters(**kwargs):
for key, value in kwargs.items():
print(f'{key} = {value}')
print_parameters(alpha=1.5, beta=9, gamma=4)
>>>
alpha = 1.5
beta = 9
gamma = 4
关键字参数的活用方法可以带来三个好处。
第一个好处是,用关键字参数调用函数可以让初次阅读代码的人更容易看懂。例如,读到 remainder(20, 7)这样的调用代码,就不太容易看出来谁是被除数 number,谁是除数 division,只有去查看 remainder 的具体实现方法才能明白。但如果改用关键字形式来调用,例如 remainder(number=20, division=7),那么每个参数的含义就相当明了。
关键字参数的第二个好处是,**它可以带有默认值,该值是定义函数时指定的。**在大多数情况下,调用者只需要沿用这个值就好,但有时也可以明确指定自己想要传的值,这样能够减少重复代码,让程序看上去干净一些。
例如,我们要计算液体流入容器的速率。如果这个容器带刻度,那么可以取前后两个时间点的刻度差 ( wight_diff ),并把它跟这两个时间点的时间差( time_diff )相除 ,就可以算出流速了。
def flow_rate(weight_diff, time_diff):
return weight_diff / time_diff
wight_diff = 0.5
time_diff = 3
flow = flow_rate(weight, time_diff)
print(f'{flow:3} kg per second')
>>>
0.167 kg per second
一般来说,我们会用每秒的千克数表示流速。但有的时候,我们还想估算更长的时间段(例如几小时或几天)内的流速效果。只需给同一个函数加一个 period 参数来表示那个时间段相当于多少秒即可。
def flow_rate(weight_diff, time_diff, period):
return (weight / time_diff) * period
这样写有个问题,就是每次调用函数时,都得明确指定 period 参数。即便我们想计算每秒钟的流速,也还是得明确指定 period 为1.
flow_per_second = flow_rate(weight_diff, time_diff, 1)
为了简化这种用法,我们可以给 period 参数设定默认值。
def flow_rate(weight_diff, time_diff, period=1):
return (weight / time_diff) * period
这样的话, period 就变成可选参数了。
flow_per_second = flow_rate(weight_diff, time_diff)
flow_per_second = flow_rate(weight_diff, time_diff, period=3600)
这个办法适用于默认值比较简单的情况。如果默认值本身要根据比较复杂的逻辑来确定(参见 第24条),那就得好好考虑一下了。
关键字参数的第三个好处是,我们可以很灵活地扩充函数的参数,而不用担心会影响原有的函数调用代码。这样的话,我们就可以通过这些新参数在函数里面实现许多新的功能,同时又无需修改早前写好的调用代码,这让程序不容易因此出现 bug。
例如,我们想继续扩充上述 flow_rate 参数的功能,让它可以用千克之外的其他重量单位来计算流速。那只需要再添加一个可选参数,用来表示 1 千克相当于多少个那样的单位即可。
def flow_rate(weight_diff, time_diff, period=1, units_per_kg=1):
return ((weight_diff * units_per_kg) / time_off) * period
新参数 units_per_kg 的默认值为1,这表示默认情况下,依然以千克为重量单位来计算。于是,以前写好的那些调用代码就不用修改了。以后调用 flow_rate 时,可以通过关键字形式这个参数指定值,以表示他们想用的那种单位。(例如磅,1 千克约等于 2.2 磅)。
pounds_per_hour = flow_rate(weight_diff, time_diff, period=3600, units_per_kg=2.2)
可选的关键字参数有助于维护向后兼容(bakcward compatibility)。这是相当重要的问题,对于接收带 *kwargs 参数的函数,也要注意向后兼容(参见 第22条)。
像 period 和 unit_per_kg 这样可选的关键字参数,只有一个缺点,就是调用者仍然能够按照位置来指定。
period_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2)
通过位置来指定可选参数,可能会让读代码的人有点儿糊涂,因为他不太清楚3600,2.2 这两个值分别指哪个量的缩放系数。所以,最好是能以关键字的形式给这些参数传值,而不要按位置去传。从设计的角度来说,还可以考虑用更加明确的方案以降低出错概率(参见 第25条)。