python中函数参数问题小究
python3中函数参数有位置参数、命名关键字参数、默认参数、非关键字可变长参数、关键字可变长参数几种。
一、位置参数
位置参数是函数的标准参数,是最常用的一种参数格式。定义函数时,由于每个参数都有自己的位置,如果不加以特殊说明,调用函数时,函数就会根据所赋参数的位置来给函数内参数赋值。
def s(x,y): print('x=',x) print('y=',y) a,b=1,0 s(a,b) 运行结果: x= 1 y= 0
调用时,函数会自动将处在x位置的a参数赋给x,把参数b赋给y。
位置参数是必选参数,调用函数时,位置参数未赋值,函数将报错。
二、命名关键字参数
格式:
def s(x,y,*,z,k)
print('x=',x)
print('y=',y)
print('z=',z)
print('k=',k)
a,b,c,d=4,3,2,1
s(a,b,k=c,z=d)
输出
x= 4
y= 3
z= 1
k= 2
此时,位置参数必须在最前,关键字参数在后,关键字参数之间可以不按顺序。
定义命名关键字参数时,需要使用*分割符,*后面的参数都是命名关键字参数。命名关键字参数在使用时,必须使用‘形参=’这种格式,不然将会报错
三、默认参数
默认参数是指在定义函数时,赋给该参数一个值。调用该函数时,如果未给该参数赋值,则函数会自动将定义时的值拿来使用。
def s(x,k='233'):
print('x=',x)
print('k=',k)
a,b,c,d=4,3
s(a)
s(a,b)
s(a,k=c)
输出:
x= 4
k= 233
x= 4
k= 3
x= 4
k= 4
可以看到,如果省略了k,则默认为是233,如果给k赋值,则以赋值为准。给k赋值时,可以采用位置参数的方式(不加想形参名字),也可以采取关键字参数的方式。
默认参数在定义时的位置必须在位置参数之后。调用的时候,也必须在位置参数之后,否则会报错。这是因为函数在给参数赋值的时候,会优先将值赋给位置参数,剩下如果有多余的值会再赋给默认参数,如果没有就采用默认值。
注意,定义默认参数一定是不可变参数。如果是可变参数,可能会出现一些错误。参考廖雪峰老师微博:
先定义一个函数,传入一个list,添加一个END
再返回:
def add_end(L=[]): L.append('END') return L
当你正常调用时,结果似乎不错:
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
当你使用默认参数调用时,一开始结果也是对的:
>>> add_end()
['END']
但是,再次调用add_end()
时,结果就不对了:
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
很多初学者很疑惑,默认参数是[]
,但是函数似乎每次都“记住了”上次添加了'END'
后的list。
原因解释如下:
Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
要修改上面的例子,我们可以用None
这个不变对象来实现:
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
现在,无论调用多少次,都不会有问题:
>>> add_end()
['END']
>>> add_end()
['END']
为什么要设计str
、None
这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
如果函数中既有命名关键字参数,又有默认参数,两个参数必须都在位置参数之后。如果赋值时,默认参数赋值不使用‘k=’这样的格式,则必须严格按照定义函数时的位置来传递参数。如果使用‘k=’这种格式,在这两只参数在传递时位置没有特殊的要求。
但是在定义函数时,如果将默认参数放在命名关键参数之后,则表明这个参数既是默认参数,也是命名关键参数。
def s(x,k=233,*,y,z,j=111): print('x=',x) print('y=',y) print('z=',z) print('k=',k) print('j=',j) a,b,c,d=4,3,2,1 s(a,z=c,y=b,k=d) 输出: x= 4 y= 3 z= 2 k= 1 j=111
四、非关键字可变长参数
非关键字可变长参数用 *变量名 来表示,定义函数时,一般用 *args 来表示。主要用来处理一些不能确定有多少个参数的问题。
函数将多余的不带关键字的参数打包成为一个元组,进行处理。*args后面的参数必须使用关键字,不然全都会被当成可变长参数,被打包到元组中。
但是,*args与默认参数之间顺序的问题也需要注意。
def s(x,y=233,*args):
print('x =',x)
print('y =',y)
for index,i in enumerate(args):
print('args[%s] = %s'%(index,i))
print(type(args))
s(1,2,3,4,5)
输出:
x = 1
y = 2
args[0] = 3
args[1] = 4
args[2] = 5
<class 'tuple'>
可以看到,该函数第一个值赋给位置参数,第二个值赋给默认参数,后面的值全都打包成为一个元组,通过for循环来显示。只要传递的参数传递的参数大于两个,就会自动给默认参数赋值,此时默认参数基本上失去了默认的意义。
也可以将默认参数放在*args的后面,但是此时给默认参数赋值时,需要加上关键字,不然就会被打包为元组,赋值给args。
def s(x,*args,y=233): print('x =',x) print('y =',y) for index,i in enumerate(args): print('args[%s] = %s'%(index,i)) s(1,2,3,4,5) 输出: x = 1 y = 233 args[0] = 2 args[1] = 3 args[2] = 4 args[3] = 5
s(1,2,3,4,y=5) 输出: x = 1 y = 5 args[0] = 2 args[1] = 3 args[2] = 4
列表或元组可以以作为参数传入*args,如果不想被二次打包,需要在列表或者元组前面加 * 。
def s(x,*args,y=233): print('x =',x) print('y =',y) for index,i in enumerate(args): print('args[%s] = %s'%(index,i)) s(1,2,*[4,5,6]) 输出: x = 1 y = 233 args[0] = 2 args[1] = 4 args[2] = 5 args[3] = 6
五、可变长的关键字参数
可变长关键字参数用 **变量名 来表示,定义函数时,一般用 **kwargs 来表示。主要用来处理一些不能确定有多少个关键字参数的问题。
函数将多余的带关键字的参数打包成为一个字典,关键字为字典的key,进行处理。**kwargs必须是放在函数形参的最后。
def s(x,*args,y=233,**kwargs): print('x =',x) print('y =',y) for index,i in enumerate(args): print('args[%s] = %s'%(index,i)) for i in kwargs: print(i,' ',kwargs[i]) print('kwargs type is ',type(kwargs)) s(1,2,a=3,b=4) 输出结果: x = 1 y = 233 args[0] = 2 a 3 b 4 kwargs type is <class 'dict'>
同样,字典也可以作为参数传入**kwargs,需要在字典前加 **
可以对比下列代码的执行结果:
def s(x,*args,y=233,**kwargs): print('x =',x) print('y =',y) for index,i in enumerate(args): print('args[%s] = %s'%(index,i)) for i in kwargs: print(i,' ',kwargs[i]) s(1,2,{'a':5}) 输出: x = 1 y = 233 args[0] = 2 args[1] = {'a': 5} #由于该字典不带关键字,会被当成一个非关键字参数打包入args。
s(1,2,a={'a':5})
输出:
x = 1
y = 233
args[0] = 2 a {'a': 5} # 此处,该字典带了关键字,会将该字典连同关键字一起进行打包,变为一个嵌套的字典。
s(1,2,**{'a':5})
输出:
x = 1
y = 233
args[0] = 2
a 5 #由于字典前加上** ,该字典直接被加入kwargs中