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])

  咦?∗" role="presentation">∗∗号怎么出现在了一个列表前面?这样对吗?


  *args*kwargs,以及单独的∗" role="presentation">∗∗,∗∗" role="presentation">∗∗∗∗到底是啥作用呢?原理是啥呢?读完这篇文章你就彻底明白了!

  • 写在前面
  • *args
    • 打包参数(pack)
    • 拆分参数(unpack)(pack)
  • **kwargs
    • 打包参数(pack)
    • 拆分参数(unpack)
  • 关于参数混用的问题

*args

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

打包参数(pack)

例1:

def foo(*number):
    print(number)

foo(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)

  我们看到了什么?给函数5个参数,成功运行了,而且输出是参数构成的元组
  我们知道,如果number前不加∗" role="presentation">∗∗号,那么很明显foo()只能接受1个参数,参数给多了少了都要报错。而加上∗" role="presentation">∗∗,就能成功运行。
  那么原理是什么呢?
  答案是:∗" role="presentation">∗∗把函数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接受走了。
  所以这里我们可以给出∗" role="presentation">∗∗作用的完整版:

  ∗" role="presentation">∗∗的作用:函数接受实参时,按顺序分配给函数形参,如果遇到带∗" role="presentation">∗∗的形参,那么就把还未分配出去的实参以元组形式打包(pack),分配给那个带∗" role="presentation">∗∗的形参。

  可以再多几个例子验证:
例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前面的参数带∗" role="presentation">∗∗,把剩下的实参都接受走了,c没有传入实参!

  到这里,∗" role="presentation">∗∗的打包(pack)就解释清楚了。
  还留一个小尾巴:args是啥?
  答案是:args仅仅是一个约定俗成的形参的写法,你写成把别的也没事,但是不利于统一形式。就像我们的例子里,一直用的number,也照样运行正确。

拆分参数(unpack)(pack)

例6:

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

bar(*[1,2,3])
1 2 3

  可以看出,∗" role="presentation">∗∗这次没有用在函数定义中,而是用在了函数调用中。在本例中的作用是啥呢?
  答案是:把打包了的实参(元组或列表),拆分(unpack)成单个的,依次赋值给函数的形参。
  在本例中,打包了的实参[1,2,3]被拆分,1赋值给了形参a,2赋值给了形参b,3复制给了形参c。

  ∗" role="presentation">∗∗拆分的作用就这么简单。理解了原理,其他的万变不离其宗。出两个题,练习一下:
  练习:以下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也有两部分构成为——∗∗" role="presentation">∗∗∗∗和kwargs。这里的重点是∗∗" role="presentation">∗∗∗∗。没错,kwargs仅仅是一个约定俗成的写法,没有其他特殊含义,换成其他的也照用不误,但是为了代码可读性,最好还是用约定俗成的。
  ∗∗" role="presentation">∗∗∗∗的作用同样也有两个—— 打包参数(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

  注意这里有个需要注意的地方,就是用∗∗" role="presentation">∗∗∗∗方式拆解字典给形参赋值时,需要字典的键名和函数形参一直,否则会报错,自己试试就知道了。

关于参数混用的问题

  位置参数,关键字参数,*args**kwargs混用是要有一定顺序的,这里我特地不写了。因为这问题本身应该不是个问题,是学习函数时应该打下的基本功。如果真的需要,可以自己再查一下,加深印象,也比在我这伸手就拿来的好。



Self-Discipline  and  Social  Commitment" role="presentation">Self-Discipline  and  Social  CommitmentSelf-Discipline  and  Social  Commitment

欢迎批评指正~~~

(0)

相关推荐

  • python函数的万能参数

    我们通过一个简单的事例来展示一下函数的万能参数,我们先写一个最简单的函数 def test(*args,**kwargs): print(args,kwargs) 然后定义两个变量 l = [1,2, ...

  • Python学习——面向对象之元类

    文章目录 什么是元类 类的创建过程--元类的引出 自定义元类 `__init__()`.`__new__()`.`__call__()`魔术方法 自定义元类使用`__call__` 自定义元类使用`_ ...

  • Python学习—装饰器

    学习Python已经有一段时间了,陆续学了一些基础部分,但是理解的不是很深刻,每过一段时间就会忘记,所以不得不写一些博客进行记录,加深自己的理解.这两个星期一直在研究装饰器,开始觉得很简单,但是只知其 ...

  • 温故而知新--day2

    温故而知新--day2 类 类与对象 类是一个抽象的概念,是指对现实生活中一类具有共同特征的事物的抽象.其实列化后称为对象.类里面由类属性组成,类属性可以分为数据属性和函数属性(函数属性又称为类方法) ...

  • Python中 *args 和 **kwargs 的含义?

    公众号新增加了一个栏目,就是每天给大家解答一道Python常见的面试题,反正每天不贪多,一天一题,正好合适,只希望这个面试栏目,给那些正在准备面试的同学,提供一点点帮助! 小猿会从最基础的面试题开始, ...

  • 关于python中if __name__ == '__main__':的理解

    调试代码的时候都会写上if __name__ == '__main__':,然后写上数据进行调试,一直没有理解到这句的含义,就照搬着写,到现在才算理解到,大概说下自己的见解. 1.在python里__ ...

  • python中的other.a怎么理解?

    表白:黑白圣堂血天使,天剑鬼刀阿修罗.  讲解对象:/python中的other.a怎么理解? 作者:融水公子 rsgz Python3 教程 Python3 教程 http://www.rsgz.t ...

  • Python中的*args和**kwargs

    在Python中的代码中经常会见到这两个词 args 和 kwargs,前面通常还会加上一个或者两个星号.其实这只是编程人员约定的变量名字,args 是 arguments 的缩写,表示位置参数:kw ...

  • Python中的多态如何理解?

    Python中多态的作用 让具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容(功能)的函数. Python中多态的特点 1.只关心对象的实例方法是否同名,不关心对象所属的类型 ...

  • 如何用外行容易理解的语言解释Python中的概念?

    Python编程学习圈 2021-07-30 现如今,编程已经成为一个非常普遍的技能,很多工作中都可以用上.于是,作为程序员,经常会被周围人(比如自己的女朋友)问到关于编程是什么的问题. 今天给大家分 ...

  • 一文理解 Python 中的变量

    " 变量让程序活起来,不再千人一面." 我们在之前的文章<Python 基本数据类型介绍>中了解了如何创建各种基本类型的数据,但是我们的例子中使用的都是"字面 ...

  • 如何理解和使用 Python 中的列表

    原创 马超 DeveloperPython 2017-10-28 阅读本篇大概需要 6 分钟. 昨天写了一篇 我到底有多么努力 之后,收到很多评论和感动. 评论中大多数人都讲到自己也在努力,我也相信只 ...

  • Python编程学习:让函数更加灵活的*args和**kwargs(设计不同数量参数的函数)的简介、使用方法、经典案例之详细攻略

    Python编程学习:让函数更加灵活的*args和**kwargs(设计不同数量参数的函数)的简介.使用方法.经典案例之详细攻略 *args和**kwargs(设计不同数量参数的函数)的简介 *arg ...