为什么想写这个
因为在实际浏览项目代码的过程中,我发现大部分人写的传参代码都太混乱了,该传或者不该传的都传进去并且混在一起,主要是我也不是很懂这部分,因此,借助这个机会,对函数的传参这部分内容详细讲解一下。
函数传参
函数传参的类型
一个完整的Python参数系统可以分成:
位置参数
默认参数
关键字参数
可变位置参数*args
可变关键字参数**kwargs
位置参数
def add(a,b):
return a + b
# 10->a 20->b
add(10,20) -> 30
此时是按照位置进行参数的绑定
关键字参数
def divide(a,b):
return a/b
divide(a=10,b=2)
divide(b=2, a=10) # 两者等价此时传参按照固定参数名来传递,而不是按照位置。
位置参数和关键字参数混用:此时位置传参要卸载关键字传参之前
def func(a,b,c):
return a+b+c
func(1,2,c=3) #合法
func(a=1,2,3) #不合法与此同时注意不能给一个参数传两次值,比如之前示例的divide函数不能写成divide(1,a=2),原因是a已经绑定给了1,就不能再赋值了。
默认参数
def power(x,n=2)
return x**n
power(3) == power(3,2)注意默认参数要写在必须参数的后面
可变位置参数
def f(*args):
print(args)
args = (10,20,30)
f(*args) == f(10,20,30)
# 输出(10,20,30) 此时args是一个元组,无论传入多少参数都会以元组的形式收集起来如果可变位置参数和位置参数之类的共同使用,前面的参数会进行匹配,剩余匹配不到的参数会被收集。
传入的args只要是一个可迭代对象就可以实现解包,无论是什么形式只要其可迭代。
可变关键字参数
def f(**kwargs):
print(kwargs)
f(name="Tom",age=20)
'''
输出:
{
"name" : "Tom",
"age" : 20
}
'''所以**kwargs用来接收没有被其他形参接收的关键字实参,而调用端使用**是展开mappint,只需要传入一个字典对象展开即可。与此同时,注意**和*两种展开方式的不同,*接收一个可迭代对象,如果这个可迭代对象是字典,字典是默认只返回key而不返回value,因此得到的args会是一个包含key但是不带value的元组。
特殊情况以及多种方式混合使用
仅关键字参数
def f(a,*,b,c):
print(a,b,c)
# 这里*后面的参数只能通过关键字传入
f(1,b=2,c=3) #合法
f(1,2,3) #非法
# 因为有的函数强制写参数名会让代码更加清晰
def connect(host, *, timeout=10, retry=3):
pass
connect(
"example.com",
timeout=5,
retry=2
)注意*args后面的参数天然就是仅关键字参数。
仅位置参数
# python3.8之后
def f(a, b, /):
print(a, b)
# 这里/表示前面的参数只能通过位置传入
f(1,2) # 合法
f(a=1,b=2) # 不合法
仅位置和仅关键字混合使用
def f(a, b, /, c, d=4, *, e, f=6):
pass
# 其中a,b仅支持位置传参,c和d支持任意传参,e和f只能用关键字传参融合使用
可以把传参用这个形式带入
def func(
a,
b,
/,
c,
d=10,
*args,
e,
f=20,
**kwargs
):
pass应该比较清楚了,本质就是把参数划分成了几个段落。注意到d实际上,因为你传入的参数必然会被d接收,实际上它的默认值不起效果,所以应用过程中,我们最好把这种具有默认值的放到后面。
如何写出更加优雅的传参
实际上传参的问题还不止这一点,还包含很多特殊情况,为了方便观察,我们最好把需要用位置传参的参数放到最前面,使用关键字传参的放到后面,这不代表一定会出现错误,比如一种特殊情况,当位置传参是用*args进行传递的时候,前面可以写关键字传参,当然这应该是为了方便收集无效的参数,但是背后原理可能更加复杂一点。所以我觉得,不用位置传参是最好的,没错,大家都用关键字吧。