python中的双星参数展开问题
在python的函数中允许有不定参数,我们对于这种参数已经习以为常了,并且这种写法很方便的,但是对于它背后的运行机制却不是很熟悉,正好最近在写项目过程中有遇到过这种双星不定参数的问题,这里也记录一下
基本使用
| # coding:utf-8
def test(name, age, *args, **kwargs):
print("args:",args)
print("kwargs:",kwargs)
print("name:", name)
print("age", age)
for i in args:
print("args:", i)
for k, v in kwargs.items():
print("kwargs:", k, v)
if __name__ == '__main__':
test('yang', 18, 'is', 'a', 'good', 'man', sex="male")
|
这个脚本的测试结果为:
| args: ('is', 'a', 'good', 'man')
kwargs: {'sex': 'male'}
name: yang
age 18
args: is
args: a
args: good
args: man
kwargs: sex male
|
我们看到,调用函数test的参数为'yang', 18, 'is', 'a', 'good', 'man', sex="male"
,python依次将yang
和18赋给了name和age,将'is', 'a', 'good', 'man'
赋给了args元组,将sex="male"
转换成了一个字典给了kwargs.
双星操作到底做了什么?
我们知道kwargs是一个字典,但是是否考虑过**kwargs
是个什么东西吗?打印一下看看。
| >>> d = {"name":"yangyanxing"}
>>> print(**d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'name' is an invalid keyword argument for this function
|
试图打印**dict
的时候却报错了,它的意思是name
并不是print函数的参数,那么标准库中的print函数都有哪些参数呢?我们来看一下print在标准库中的定义
print(self, *args, sep=' ', end='\n', file=None)
,
它确实没有一个name的形参,如果使用print(**d)是不是就和print(name="yangyanxing")的效果是一样的呢?
| >>> d = {"name":"yangyanxing"}
>>> print(**d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'name' is an invalid keyword argument for this function
>>> print(name="yangyanxing")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'name' is an invalid keyword argument for this function
|
它们报了相同的错误,这里们先假定使用**dict
其实就是将一个字典里的各项依次展开,比如d={'name':"yangyanxing"},使用**d
则返回name="yangyanxing"的形式。
我们写一个函数检测一下
| # coding:utf-8
def test(name, age):
print("name:", name)
print("age", age)
if __name__ == '__main__':
d = {"name":"杨彦星",'age':18}
test(**d)
|
这里test函数需要两个参数,name和age,但是我们在传参数的时候并没有直接传这两个参数,而是传一个**字典
,**
号会把字典展开成key=value的形式并且传递到test函数中。于是得到了以下的输出
如果所传入的字典中包含了不只包含了函数所需要的参数,还有更多的参数呢?
| # coding:utf-8
def test(name, age,**kwargs):
print("name:", name)
print("age", age)
print("kwargs:", kwargs)
if __name__ == '__main__':
d = {"name":"杨彦星",'age':18,
'sex':"male",'school':"bjut"}
test(**d)
|
它得到的结果是
| name: 杨彦星
age 18
kwargs: {'school': 'bjut', 'sex': 'male'}
|
可以看到,它将字典里多余的键值对放到了kwargs字典中了。
同理,一个*
也是自动展开列表和元组
| # coding:utf-8
def test(name,age,*args,**kwargs):
print("args:", args)
print("kwargs:",kwargs)
print("name:",name)
print('age:',age)
if __name__ == '__main__':
d = {"name1":"杨彦星",'age1':18}
l = ['a','b','c']
t = ('d','e','f')
test(*l,*t,**d)
|
这里我们将列表l和元组t展开传到test函数中,相当于test('a','b','c','d','e','f',name1="杨彦星",age1=18)
,于是得到以下输出
| args: ('c', 'd', 'e', 'f')
kwargs: {'age1': 18, 'name1': '杨彦星'}
name: a
age: b
|
这里有几个问题需要注意下,由于**kwargs
只能定义在函数参数的最后,它后面不能再有形参了,但是*
参数可以不限制参数的位置,所以上面的代码在定义d的时候不能再有name和age的定义了,因为在传参的时候,已经将'a'赋给了name,'b'赋给了age,所以如果之后再有name的定义则会报参数重复定义的错误。但是却可以如下的定义
| # coding:utf-8
def test(*args,name,age,**kwargs):
print("args:", args)
print("kwargs:",kwargs)
print("name:",name)
print('age:',age)
if __name__ == '__main__':
d = {"name":"杨彦星",'age':18,'sex':'male'}
l = ['a','b','c']
t = ('d','e','f')
test(*l,*t,**d)
|
因为把*args
放到了第一个参数,所以在调用函数时为test('a','b','c','d','e','f',name="杨彦星",age=18,sex='male')
,此时如果d的定义的时候没有name和age也会报错。
我觉得这种写法并不是很优雅,但是看到过有些库里有过类似的写法,所以之后看到不用太困惑,只需要理解**
和*
只是把后面的字典和列表或者元组进行展示即可。