编写高质量Python (第23条) 用关键字来表示可选的行为

第23条 用关键字来表示可选的行为

​ 与其他编程语言样,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条)。

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值