python中*args和**kwargs的理解 - Go语言中文社区

python中*args和**kwargs的理解


读代码的过程中经常见到这种含*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

args有两部分构成为——∗和args。这里的重点是∗。
  所以为了讲清楚
args,我们要追根溯源——理解∗的作用。
  这里敲黑板,重点来了,这也是很多博客写的没有写到的地方:∗的作用,有2个—— 打包参数(pack)和拆分参数(unpack)!

打包参数(pack)

例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,也照样运行正确。

拆分参数(unpack)(pack)

例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是把字典的键值拆成单个的,依次赋值给函数的形参。

打包参数(pack)

例10

def bar(**number):
    print(number)

bar(a=1, b=2, c=3)
{'a': 1, 'b': 2, 'c': 3}

拆分参数(unpack)

例11

def bar(a, b, c):
    print(a,b,c)

bar(**{'a': 1, 'b': 2, 'c': 3})
1 2 3

原文连接[https://blog.csdn.net/lllxxq141592654/article/details/81288741#写在前面]

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/tjy451592402/article/details/85095359
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-27 23:28:18
  • 阅读 ( 963 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢