令函数接收可变的位置参数(*args
),能够让代码更加清晰。
案例1:定义log
函数打印输出信息,如果参数个数固定,那么必须将参数打包成列表传进去。
def log(message,values):
if not values:
print(message)
else:
values_str = ', ' .join(str(x) for x in values)
print("%s:%s"%(message,values_str))
if __name__=='__main__':
log('My numbers are',[1,3])
但是当没有参数输入时,也必须传入一个空列表[]
,比较麻烦。为此可以采用接收可变的位置参数。
def log(message,*values):
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print("%s:%s"%(message,values_str))
if __name__=='__main__':
log('My numbers are',[1,3,9])
Python对传入的列表在*values
接收后,逐个解析,作为其输入的位置参数。
接收可变的位置参数,会带来以下两个问题:
第一个问题,变长参数在传给函数时,总是要先转化为元组(tuple)。意味着,如果某个函数的*
操作符参数为生成器,那么当调用该函数时,Python就必须将该生成器完整的迭代一轮,并把生成器所生成的每一个值,都放入元组中,可能会带来内存的大量消耗,导致程序崩溃。
def my_generator():
for i in range(10):
yield i
def my_func(*args):
print(args)
if __name__=='__main__':
it = my_generator()
my_func(*it)
输出结果:
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
因此,只有我们确认输入参数个数比较少时,才应该令函数接收*args
式的变长参数。
第二个问题,如果以后要给函数添加新的位置参数,那就必须修改原来调用该函数的那些旧代码。若是只给参数列表前方添加新的位置参数,而不更新现有的调用代码,则会产生难以调试的错误。
def log(sequence,message,*values):
if not values:
print"%s:%s"%(sequence,message))
else:
values_str = ', '.join(str(x) for x in values)
print("%s:%s:%s"%(sequence,message,values_str))
if __name__ == '__main__':
log(1,"Favorites",7,33) #新代码
log("Favorites",7,33) #旧代码
问题在于,之前写好的旧代码将7
原本是values
的一部分,却赋值给了message
,还不会产生错误,导致代码异常很难追踪。
为了避免这种情况,应该使用只能以关键字形式指定的参数,来扩展这种接受*args
的函数。
下面介绍关键字参数。
Python的所有位置参数都可以按照关键字传递。
def remainder(number,divisor):
return number % divisor
if __name__=='__main__':
# 下面几种方式等效
remainder(20,7)
remainder(number=20,divisor=7)
remainder(20,divisor=7)
remainder(divisor=7,number=20)
remainder(number=20,7)# error
需要注意的是:
- 位置参数必须在关键字参数之前。
- 每个参数只能指定一次
remainder(20,number=7) ## Error
使用关键字参数的优点如下:
1.可以使得函数调用者清晰的明确各传入参数的意义。
2.可以在函数定义中提供默认值
。大部分情况下,函数调用者只需要使用这些默认值就够了,若要特别地指定某个参数,则可以指定相应的关键字参数,这样可以减少重复代码,使得代码变得简洁。
3.提供了一种扩充函数参数的有效方式,使得扩充之后的函数依然能与原有的那些相兼容。
##注意位置参数在关键字参数之前
def flow_rate(weight_diff,time_diff,period=1,units_per_kg=1):
return ((weight_diff * units_per_kg)/time_diff) * period