python中*args和**kwargs的理解
写在前面 我得先发个牢骚! 在python中经常见到*args和**kargs的用法,让初学者很懵逼,我自己在查资料的过程中,发现网上绝大多数的博客都写的很片面,要么写的不清楚,这让初学者更懵逼!比如∗∗*的用法,很多博客是这样描述的“让函数接收不定个参数”,然后一个例子,貌似懂了,但是没有讲清楚里面的机理,更没有说明白∗∗*除了这种接收不定个参数(pack)的用法,还有将参数拆解(u....
写在前面
读代码的过程中经常见到这种含*args
和**kwargs
的表达:
比如这个该输出什么呢?
def foo(*args):
print(args)
foo(1, 2, 3, 4, 5)
这个呢?
def foo(a, *args):
print('a:', a)
print('args:', args)
foo(1, 2, 3, 4, 5)
还有这个呢?
def bar(a,b,c):
print(a,b,c)
bar(*[1,2,3])
咦? ∗ * ∗号怎么出现在了一个列表前面?这样对吗?
*args
和**kwargs
,以及单独的
∗
*
∗,
∗
∗
**
∗∗到底是啥作用呢?原理是啥呢?读完这篇文章你就彻底明白了!
*args
有两部分构成为——*
和args
。这里的重点是*
。
所以为了讲清楚*args
,我们要追根溯源——理解*
的作用。
这里敲黑板,重点来了,这也是很多博客写的没有写到的地方:
∗
*
∗的作用,有2个—— 打包参数(pack)和拆分参数(unpack)!
*argc
打包参数
例1:
def foo(*number):
print(number)
foo(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)
我们看到了什么?给函数5个参数,成功运行了,而且输出是参数构成的元组。
我们知道,如果number前不加
∗
*
∗号,那么很明显foo()只能接受1个参数,参数给多了少了都要报错。而加上
∗
*
∗,就能成功运行。
那么原理是什么呢?
答案是:
∗
*
∗把函数foo()接受到的多个参数1,2,3,4,5
,打包成了元组(1,2,3,4,5)
,赋值给了形参number。
我们可以验证一下:
例2:
def foo(*number):
for i in number:
print(i)
print(type(number))
foo(1, 2, 3, 4, 5)
1
2
3
4
5
<class 'tuple'>
从例2可以看出,number确实被赋予了(1,2,3,4,5)
这个实参。
说话要讲道理,详情参见python官方文档,这里粘个图过来:
例3:
def foo(a, *number):
print('a:', a)
print('number:', number)
for i in number:
print(i)
print(type(number))
foo(1, 2, 3, 4, 5)
a: 1
number (2, 3, 4, 5)
2
3
4
5
<class 'tuple'>
从例3可以看出,number接受到的实参变成了(2,3,4,5)
,第一个参数1
被形参a
接受走了。
所以这里我们可以给出
∗
*
∗作用的完整版:
∗ * ∗的作用:函数接受实参时,按顺序分配给函数形参,如果遇到带 ∗ * ∗的形参,那么就把还未分配出去的实参以元组形式打包(pack),分配给那个带 ∗ * ∗的形参。
可以再多几个例子验证:
例4:
def foo(a, b, *number):
print('a:', a)
print('b:', b)
print('number:', number)
for i in number:
print(i)
print(type(number))
foo(1, 2, 3, 4, 5)
a: 1
b: 2
number: (3, 4, 5)
3
4
5
<class 'tuple'>
例5:
def foo(a, b, *number, c):
print('a:', a)
print('b:', b)
print('c:', c)
print('number:', number)
for i in number:
print(i)
print(type(number))
foo(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "C:/Users/PycharmProjects/untitled10/test19.py", line 11, in <module>
foo(1, 2, 3, 4, 5)
TypeError: foo() missing 1 required keyword-only argument: 'c'
注意例5我特地找了个报错的例子。自己分析一下为啥会报错。答案是:c前面的参数带 ∗ * ∗,把剩下的实参都接受走了,c没有传入实参!
到这里,
∗
*
∗的打包(pack)就解释清楚了。
还留一个小尾巴:args是啥?
答案是:args仅仅是一个约定俗成的形参的写法,你写成把别的也没事,但是不利于统一形式。就像我们的例子里,一直用的number,也照样运行正确。
拆分参数
例6:
def bar(a,b,c):
print(a,b,c)
bar(*[1,2,3])
1 2 3
可以看出,
∗
*
∗这次没有用在函数定义中,而是用在了函数调用中。在本例中的作用是啥呢?
答案是:把打包了的实参(元组或列表),拆分(unpack)成单个的,依次赋值给函数的形参。
在本例中,打包了的实参[1,2,3]
被拆分,1赋值给了形参a,2赋值给了形参b,3复制给了形参c。
∗
*
∗拆分的作用就这么简单。理解了原理,其他的万变不离其宗。出两个题,练习一下:
练习:以下3段程序中,哪个可以正常运行?
例7:
def bar(a,b):
print(a,b)
bar(*[1, 2, 3])
例8:
def bar(a, b, c, d):
print(a, b, c, d)
bar(*[1, 2, 3])
例9:
def bar(a, b, c, d=10):
print(a, b, c, d)
bar(*[1, 2, 3])
答案是只有例9可以正常运行。因为按照我们讲的原理,例7的实参3没有对应的形参接受,例8的形参d没有实参赋值。
**kwargs
打包参数
上边*args
学懂了**kwargs
也就很容易明白了。
**kwargs
也有两部分构成为——**
和kwargs
。这里的重点是
∗
∗
**
∗∗。没错,kwargs仅仅是一个约定俗成的写法,没有其他特殊含义,换成其他的也照用不误,但是为了代码可读性,最好还是用约定俗成的。
∗
∗
**
∗∗的作用同样也有两个—— 打包参数(pack)和拆分参数(unpack)!
但是区别还是有的,简单来说就是:
打包(pack):*args
是把多个位置参数打包成元组,**kwargs
是把多个关键字参数打包成字典。
拆分(unpack):*args
是把打包了的参数拆成单个的,依次赋值给函数的形参,**kwargs
是把字典的键值拆成单个的,依次赋值给函数的形参。
例10
def bar(**number):
print(number)
bar(a=1, b=2, c=3)
{'a': 1, 'b': 2, 'c': 3}
例11
def bar(a, b, c):
print(a,b,c)
bar(**{'a': 1, 'b': 2, 'c': 3})
1 2 3
注意这里有个需要注意的地方,就是用 ∗ ∗ ** ∗∗方式拆解字典给形参赋值时,需要字典的键名和函数形参一直,否则会报错,自己试试就知道了。
位置参数,关键字参数,*args
,**kwargs
混用是要有一定顺序的,这里我特地不写了。因为这问题本身应该不是个问题,是学习函数时应该打下的基本功。如果真的需要,可以自己再查一下,加深印象,也比在我这伸手就拿来的好。
S e l f - D i s c i p l i n e a n d S o c i a l C o m m i t m e n t Self\text{-}Discipline \ \ and \ \ Social \ \ Commitment Self-Discipline and Social Commitment
欢迎批评指正~~~
更多推荐
所有评论(0)