python基础 - 位置参数、默认参数、可变参数、关键字参数
大家好,我是W
在看源码的时候经常看到各式各样的参数,有的参数有默认值,有的参数没有默认值,有的参数连参数的名都没有,只能看到*args
和**kwargs
。这些东西到底有什么区别,怎么使用呢?接下来我们学习的顺序是位置参数、默认参数、可变参数、关键字参数。
位置参数
位置参数顾名思义就是与位置有关的参数,在调用函数的时候需要根据传入参数的位置来一一对应参数。并且,位置参数是不可缺少的,即不传入完整的参数是会报错的。
下面是我们平常看到的函数最普通的形式,普通到我们甚至不知道他的参数还有个名叫位置参数:
def add(num1, num2):
return num1 + num2
if __name__ == '__main__':
# add 函数的两个参数不可缺省
# 且传入的参数值与他们的位置有关, 即main函数中add的num1的位置放的1,则在add函数的num1就是1
sum = add(1, 2)
若调用add函数时传入参数缺省了,则会报TypeError: add() missing 1 required positional argument: 'num2'
,即却是一个要求的未知参数num2。
总结就是位置参数就是与函数参数位置有关的参数,且函数的位置参数不可缺省,必须传满。
默认参数
默认参数顾名思义就是与默认值有关的参数,在位置参数中缺省会报错,为了增加函数的鲁棒性,也为了传参时在可以使用默认设置的情况下不用传那么多参数,参数可以设置默认值,这样即使缺省了该参数也不会报错了。
常见的默认参数函数时这样的:
def minus(num1, num2=0):
return num1 - num2
if __name__ == '__main__':
# 即使num2缺省也不会报错
# 注意 定义函数时默认参数一定要在位置参数之后
ans = minus(1)
其实也是相当好理解的,若默认参数可以放在位置参数之前,那我传参的个数少于参数个数,解释器到底是将参数给位置参数还是给默认参数?例如:
def minus2(num1=1, num2):
return num1 - num2
if __name__ == '__main__':
# 若给num1,则num2缺省了,不就报错了?
# 若给num2,我怎么知道你是不是要给num1赋值?
ans = minus2(1)
总结就是默认参数就是需要设置默认值的参数,默认参数必须放在位置参数之后,当需要使用默认值时可以不传默认参数。
关键字参数
其实关键字参数与默认参数就是同一个概念,在不同视角下的不同命名。
在调用函数时以等号键值对的形式传参,这个参数就叫做关键字参数,即add(1,b=2)
,1
为位置参数,b=2
为关键字参数。在定义函数的视角来看,add(a,b=3)
,a
为位置参数,b=3
为默认参数,默认值为3。
可变参数
可变参数包含了两种参数传递方式,*args
包裹位置传递和**kwargs
包裹关键字传递两种方式。在定义函数时有可能出现不确定需要传递的参数的个数的情况,这时可变参数将会给我们带来极大地便利。
*args
包裹位置传递
这回直接结合上面两种参数来看:
def fun(num, num2=1, *args):
print(args) # (3, 4, 5)
print(*(args)) # 3 4 5
if __name__ == '__main__':
# 1和2 分别对应位置参数与关键字参数
# 剩余的3 4 5都传递给了 *args
# 通过print(args)可以看到这个参数装的是一个元组,里面都是剩余的没对应上的参数
# 通过 *(args)拆包可以直接将里面的参数获取
fun(1, 2, 3, 4, 5)
从参数定义的位置来看,函数**参数从左到右依次是位置参数、关键字参数(默认参数)、可变参数(*args)
**这个位置是固定的,即不能乱改,原因类似于上面的默认参数必须在位置参数之后。
总结就是*args
的位置需要放在所有参数之后,它可以容纳所有未对应上位置的实参,且传递参数的时候会被封装成一个元组形式,可以通过拆包的方式获取里面的参数。
**kwargs
包裹关键字传递
同样的,直接从代码里看:
def fun(num, num2=1, *args, **kwargs):
print(args) # (3, 4, 5)
print(*(args)) # 3 4 5
# 直接打印kwargs可以看到传来的是一个字典 即前面的3 4 5 都丢进了*args里,而**kwargs装的是没有对应上的关键字参数
print(kwargs) # {'name': 'ali', 'age': 100}
# 通过拆包我们看到里面包含的关键字有name age
print(*(kwargs)) # name age
# 通过字典的方式获取关键字对应的值
print(kwargs['name'], kwargs['age']) # ali 100
if __name__ == '__main__':
fun(1, 2, 3, 4, 5, name='ali', age=100)
翻源码
先来看下面这段源码,来自于pyplot的barbs函数:
@docstring.copy(Axes.barbs)
def barbs(*args, data=None, **kw):
return gca().barbs(
*args, **({"data": data} if data is not None else {}), **kw)
显然,被打脸了,data=None
作为默认参数是可以放再*args
后面,那么我们仿一下这个函数,看看它传进来的值都是这么赋值的。
def fun2(*args, data=None, **kwargs):
print(args) # (1, 2, 3, 4)
print(data) # 123
print(kwargs) # {'name': 'ali', 'age': 100}
if __name__ == '__main__':
# 1 2 3 4都传给了*args
# data=123 作为关键字参数传给了data=None 从而后面的kwargs就不能再有data这个关键字了 否则会报重复关键字错误
# 在确定后面没有默认参数后,将接下来的关键字参数都丢给了**kwargs
fun2(1, 2, 3, 4, data=123, name='ali', age=100)
接着上面的源码,它调用了gca().barbs
并返回这个函数的返回值,来看看这个函数是怎么接收参数的:
def barbs(self, *args, **kw):
# 可以看到这个函数只有*args和**kwargs
那么再来仿一个函数:
def fun2(*args, data=None, **kwargs):
print(args) # (1, 2, 3, 4)
print(data) # 123
print(kwargs) # {'name': 'ali', 'age': 100}
# 可以看到*args的参数直接丢给fun3了
# 但是data做了一个简单的判断封装成了字典 跟着**kwargs一起丢给fun3的**kwargs
fun3(*args, **({'data': data} if data is not None else {}), **kwargs)
def fun3(*args, **kwargs):
print(args) # (1, 2, 3, 4)
print(kwargs) # {'data': 123, 'name': 'ali', 'age': 100}
if __name__ == '__main__':
fun2(1, 2, 3, 4, data=123, name='ali', age=100)
从这个例子能学到什么呢?
首先要纠正错误说法,默认参数也是可以放在*args
后面,想想也是没错的,因为编译器可以识别出后面的关键字参数已经不是*args
的内容了,接下来遇上了默认参数,一一赋值给关键字参数,剩下的再给**kwargs
就可以了。
其次,**kwargs
是可以接收多个字典并且封装成一个大字典的,他的过程就是将所有关键字参数、字典都拆解然后再重新封装成字典的过程,最终在fun3中接收到的就是一个大字典。
总结
看了那么多,最终只需要搞清楚这些参数的特点和要求就可以了,具体实际的应用还是需要看使用者的操作。我觉得我还是讲的不够清晰,不过不要紧我还是给大家找了一些相关资料,大家可以自行补充。