在python的函数中允许有不定参数,我们对于这种参数已经习以为常了,并且这种写法很方便的,但是对于它背后的运行机制却不是很熟悉,正好最近在写项目过程中有遇到过这种双星不定参数的问题,这里也记录一下

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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")

这个脚本的测试结果为:

1
2
3
4
5
6
7
8
9
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是个什么东西吗?打印一下看看。

1
2
3
4
5
>>> 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”)的效果是一样的呢?

1
2
3
4
5
6
7
8
9
>>> 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”的形式。

我们写一个函数检测一下

1
2
3
4
5
6
7
8
9
10
# 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函数中。于是得到了以下的输出

1
2
name: 杨彦星
age 18

如果所传入的字典中包含了不只包含了函数所需要的参数,还有更多的参数呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
# 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)

它得到的结果是

1
2
3
name: 杨彦星
age 18
kwargs: {'school': 'bjut', 'sex': 'male'}

可以看到,它将字典里多余的键值对放到了kwargs字典中了。

同理,一个*也是自动展开列表和元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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),于是得到以下输出

1
2
3
4
args: ('c', 'd', 'e', 'f')
kwargs: {'age1': 18, 'name1': '杨彦星'}
name: a
age: b

这里有几个问题需要注意下,由于**kwargs 只能定义在函数参数的最后,它后面不能再有形参了,但是*参数可以不限制参数的位置,所以上面的代码在定义d的时候不能再有name和age的定义了,因为在传参的时候,已经将’a’赋给了name,’b’赋给了age,所以如果之后再有name的定义则会报参数重复定义的错误。但是却可以如下的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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也会报错。

我觉得这种写法并不是很优雅,但是看到过有些库里有过类似的写法,所以之后看到不用太困惑,只需要理解***只是把后面的字典和列表或者元组进行展示即可。