目录

python中的双星参数展开问题

在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也会报错。

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

  • 文章标题: python中的双星参数展开问题
  • 本文作者: 杨彦星
  • 本文链接: https://www.yangyanxing.com/article/kwargs_in_python.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。