理解‘*‘,‘*args‘,‘**‘,‘**kwargs‘

原文Understanding ‘*’, ‘*args’,’**‘and’**kwargs’

刚开始学习python的时候,对有关args,kwargs,*和**的使用感到很困惑。相信对此感到疑惑的人也有很多。我打算通过这个帖子来排解这个疑惑(希望能减少疑惑)。

让我们通过以下5步来理解:

  1. 通过一个函数调用来理解’*'的作用
  2. *通过一个函数的定义来理解’args’的含义
  3. 通过一个函数的调用来理解’**'的作用
  4. *通过一个函数的定义来解’*kwargs’的含义
  5. 通过一个应用实例来说明’args’,'kwargs’应用场景以及为何要使用它

通过一个函数调用来理解’*'的作用

定义一个含三个位置参数的函数"fun".

>>> def fun(a,b,c):
...     print a,b,c
... 

传三个位置参数调用此函数

>>> fun(1,2,3)
1 2 3		#输出

可以看到出入三个位置参数调用此函数,会打印出三个参数

现在我们定义一个含三个整数的数列,并使用’*’

>>> l = [1,2,3]
>>> fun(*l)
1 2 3		#输出

‘*’ 做了什么?

它拆开数列’l’的数值作为位置参数,并吧这些位置参数传给函数’fun’来调用。

因此拆数列、传位置参数意味着fun(*l)与fun(1,2,3)是等效的,因为l = [1,2,3]。

试着数列中使用其他数值

>>> l = [4,8,0]
>>> fun(*l)
4 8 0		#输出

接下来我们试着在数列中放四个数值,调用函数会出现什么情况呢

>>> l = [3,6,9,1]
>>> fun(*l)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: fun() takes exactly 3 arguments (4 given)

在这次调用中我们并没有得到合适的结果,触发了TypeWrror异常。很容易看到错误内容"fun() takes exactly 3 arguments (4 given)".

为什么会发生这种情况呢?

数列’l’含有四个数值.因此,我们试图调用’fun(*l)’,'l’中数值拆开传给函数fun作为位置参数。但是,'l’中有四个数值,调用’fun(*l)‘相当于调用’fun(3,6,9,1)’,又因为函数’fun’定义中只用三个位置参数,因此我们得到这个错误。同理,同样的步骤,数列’l’中有两个数值情况,注意error内容。

>>> l = [7,4]
>>> fun(*l)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: fun() takes exactly 3 arguments (2 given)

'*l’与位置参数混合使用

>>> fun(23, *l)
23 7 4

在这里,我们给出一个位置参数23,和从数列’l’拆除的两个数值7和4,因此三个参数23,7和4传给了函数’fun’

通过一个函数的定义来理解’*args’的含义

修改函数的定义:

>>> def fun(*args):
...     print args
... 

传一个位置参数调用此函数

>>> fun(13)
(13,)

传多个参数调用此函数

>>> fun(11,93,43)
(11, 93, 43)

'*args’在函数定义中是做什么用的?

它接收元组作为位置参数,而非是常见的参数列表。在这里,"args"是个元组。在我们解释中不要担心"常见的参数"这部分的理解,这个会在接下来的例子中逐渐明了。在上个例子中,调用函数打印"args"时,他会打印元组中包含的所有数值。

我们重新定义函数,"*args"与"常规参数列表"混合使用

>>> def fun(a, *args):
...     print "a is ", a
...     print "args is ", args
... 

在这个函数定义中,参数"a"代表"常规参数列表"。
传四个位置参数调用此函数:

>>> fun(11,12,34,43)
a is  11
args is  (12, 34, 43)

很容易看到,‘a’打印出为11,即第一个位置参数。‘a’之后只一个参数’*args’.因此,'args’接收除常规参数之外的位置参数作为元组。因此元组args作为元组接收12,34和43。

我们也可以传一个位置参数来调用此函数:

>>> fun(91)
a is  91
args is  ()

在这里,我们传的唯一一个参数分配给了常规参数’a’.因此,'args’接收到一个空元组。

既然我们获取了"args",我们可以提取需要的数值来做我们想做的事情。重新定义"fun":

>>> def fun(a, *args):
...     print a
...     print "args can receive a tuple of any number of arguments, let's print all that."
...     for arg in args:
...             print arg
... 

现在我们传任意个参数来调用此函数:

>>> fun(1,5,6,7)
1
args can receive a tuple of any number of arguments, let's print all that.
5
6
7

'args’既然是元组,我们就可以遍历它。

现在我们考虑使用所有能得到的参数的场景。我们需要使用两个函数,第一个函数带有任意个参数,并通过另外一个函数计算除第一参数的其他参数之和。奇怪的用例,但我们只需回顾我们目前所做的。我们的目的就是在一个函数中获取可变参数,并把这些参数餐给另一个函数。

第一步我们写一个函数计算和。在这个用例中,这个函数会在第一个函数中应用。

>>> def calculate_sum(*args):
...     return sum(args)
... 

在这个函数中,我们使用内建函数’sum’,它使用元组或数列作为参数,返回元组所有元素的和。从函数的定义可以看出’args’接收包含传给此函数位置参数的元组.因此,'args’是一个元组,简介的作为函数’sum’的参数。接下来定义另外一个函数,该函数有任意个参数,并利用上一个函数来计算除第一个参数之外的其他参数的和。

>>> def ignore_first_calculate_sum(a,*iargs):
...     required_sum = calculate_sum(*iargs)
...     print "sum is ", required_sum
... 

我们可以传任意个参数给这个函数。第一个参数被常规参数’a’接收,其他参数被’iargs’作为元组接收。正如我们考虑的案例,计算除第一个参数之外的其他参数的和。因此,我们用’a’接收第一个参数,‘iargs’是包含其他参数的元组。我们用到函数’calculate_sum’,但’calculate_sum’需要多个位置参数作为元组传给’args’。所以在函数’ignore_first_calculate_sum’需要拆元组’iargs’,然后将元素作为位置参数传给’calculate_sum’.注意,用’*'拆元组。

所以,我们这样调用’required_sum=calculate_sum(*iargs)’.

'required_sum=calculate_sum(iargs)‘不能这么调用,因为传给’calculate_sum’之前我们需要unpack数值。不使用’*'将不会unpack数值,也就不能执行想要的动作。调用函数如下:

>>> ignore_first_calculate_sum(12, 1,4,5)
sum is  10
>>> ignore_first_calculate_sum()
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: ignore_first_calculate_sum() takes at least 1 argument (0 given)

得到想要的结果。

通过一个函数的调用来理解’**'的作用

定义一个三个参数的函数,并用多种方式调用:

>>> def fun(a, b, c):
...     print a, b, c
... 
>>> fun(1,5,7)
1 5 7
>>> fun(a=1,b=5,c=7)
1 5 7

使用"*“调用函数,这种方式我们需要一个字典.注意:**在函数调用中使用”*",我们需要元组;在函数调用中使用"*",我们需要一个字典**

>>> d={'b':5, 'c':7}
>>> fun(1, **d)
1 5 7

在函数调用中"**"做了什么?

它unpack字典,并将字典中的数据项作为键值参数传给函数。因此,"fun(1, **d)"的写法与"fun(1, b=5, c=7)"等效.
为了更好的理解再多举几个例子:

>>> d = {'c':3}
>>> fun(1, 4, **d)
1 4 3
>>> d = {'a':7, 'b':3, 'c':8}
>>> fun(**d)
7 3 8

让我们制造一些错误:

>>> d = {'a':7, 'b':3, 'c':8, 'd':90}
>>> fun(**d)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: fun() got an unexpected keyword argument 'd'

这次调用等同于’fun(a=7, b=3, c=8, d=90)’,但函数只需要三个参数,因此我们得到TypeError

>>> d = {'a':7, 'b':3,'d':90}
>>> fun(**d)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: fun() got an unexpected keyword argument 'd'

fun(**d)等同于fun(a=7, b=3, d=90).传给函数"fun"想要的参数个数,但参数列表中并没有’d’,调用中’d’键值参数传给函数导致TypeError.

So, “**” unpacks the dictionary i.e the key values pairs in the dictionary as keyword arguments and these are sent as keyword arguments to the function being called. “*” unpacks a list/tuple i.e the values in the list as positional arguments and these are sent as positional arguments to the function being called.

通过函数定义来理解’**kwargs’的含义

重定义函数"fun":

>>> def fun(a, **kwargs):
...     print a, kwargs
... 

此函数只用一个位置参数,因为常规参数列表中只有一个变量’a’.但是通过"**kwargs",可以传多个键值参数。

>>> fun(1, b=4, c=5)
1 {'c': 5, 'b': 4}
>>> fun(45, b=6, c=7, d=8)
45 {'c': 7, 'b': 6, 'd': 8}

在函数定义中"**kwargs"意味着什么?
用"**kwargs"定义函数,kwargs接收除常规参数列表职位的键值参数字典。在这里’kwargs’是个字典。

重新定义函数:

>>> def fun(a, **kwargs):
...     print "a is ", a
...     print "We expect kwargs 'b' and 'c' in this function"
...     print "b is ", kwargs['b']
...     print "c is ", kwargs['c']
... 
>>> fun(1, b=3,c=5)
a is  1
We expect kwargs 'b' and 'c' in this function
b is  3
c is  5

错误调用:

>>> fun(1, b=3, d=5)
a is  1
We expect kwargs 'b' and 'c' in this function
b is  3
c is 
Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 5, in fun
KeyError: 'c'

上面的调用,位置参数’a’和键值参数’b’都打印出来了。传入的其他键值参数是’d’,函数需要键值参数’c’,并从字典’kwargs’获取。但没有传入键值’c’,引发KeyError.如果传入了键值’c’就不会引发这个错误

>>> fun(1, b=3, d=5, c=9)
a is  1
We expect kwargs 'b' and 'c' in this function
b is  3
c is  9

由于’**kwargs’在函数参数列表中,我们可以传任意个键值参数。上面的调用传入了"d",但函数并没用到。

另外一个错误:

>>> fun(1, {'b':2, 'c':34})
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: fun() takes exactly 1 argument (2 given)

正如错误提示,函数’fun’只需要一个位置参数,却给了两个。尽管’kwargs’接收键值参数作为一个字典,但你不能传一个字典作为位置参数给’kwargs’.你可以像下面那样调用:

>>> fun(1, **{'b':2, 'c':34})
a is  1
We expect kwargs 'b' and 'c' in this function
b is  2
c is  34

在一个字典前使用"**"可以unpack字典,传字典中的数据项作为键值参数。

通过一个应用实例来说明’args’,'kwargs’应用场景以及为何要使用它

在任何时候继承类和重写方法的,我们应当用到’*args’和’**kwargs’将接收到的位置参数和键值参数给父类方法。通过实例我们更好的理解

>>> class Model(object):
...     def __init__(self, name):
...             self.name = name
...     def save(self, force_update=False, force_insert=False):
...             if force_update and force_insert:
...                     raise ValueError("Cannot perform both operations")
...             if force_update:
...                     print "Updated an existing record"
...             if force_insert:
...                     print "Created a new record"
... 

定义一个类,我们可以创建类的对象,类的对象有一个方法’save()’.假设类的对象可以通过save()方法保存到数据库中。通过函数save()参数来决定是否在数据库中创建一条记录或者更新现存的记录。
构造一个新类,类有’Model’的行为,但我们只有检查一些条件后才会保存这个类的对象。这个新类继承’Model’,重写’Model’的’save()’

>>> class ChildModel(Model):
...     def save(self, *args, **kwargs):
...             if self.name=='abcd':
...                     super(ChildModel, self).save(*args, **kwargs)
...             else:
...                     return None
... 

实际上对应的保存动作发生在’Model’的’save’方法中。所以我们调用子类的的’save()'方法而非’Model’的方法.子类ChildModel的’save()'接收任何父类save()需要的参数,并传给父类方法。因此,子类’save()'方法参数列表中有"*args"和"**kwargs",它们可以接收任意位置参数或键值参数,常规参数列表除外。

下面创建ChildModel实体并保存:

>>> c=ChildModel('abcd')
>>> c.save(force_insert=True)
Created a new record
>>> c.save(force_update=True)
Updated an existing record

这里传兼职参数给对象的save()方法。调用的是子类的save(),It received a dictionary containing the keyword argument in “kwargs”. Then it used “**” to unpack this dictionary as keyword arguments and then passed it to the superclass save(). So, superclass save() got a keyword argument ‘force_insert’ and acted accordingly.

Keyword-Only Arguments 和 Positional-Only Parameters

相关的PEP

  • PEP 3102 – Keyword-Only Arguments
  • PEP 457 – Notation For Positional-Only Parameters
  • PEP 570 – Python Positional-Only Parameters
def name(positional_only_parameters, /, positional_or_keyword_parameters,*, keyword_only_parameters):

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508684.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MongoDb随笔,PyMongo简单使用

安装MongoDb 【更新2021-07-06】 https://www.mongodb.com/try/download/community 下载对应系统的软件版本&#xff08;CentOS7.9 mongod 4.4.6&#xff09;rpm -ivh mongodb-org-server-4.4.6-1.el7.x86_64.rpm安装服务systemctl start mongod启动服务rpm -ivh mongodb-org…

python中classmethod与staticmethod的差异及应用

类中三种函数的应用 #!/usr/bin/env python # -*- coding: utf-8 -*-class TClassStatic(object):def __init__(self, data):self.data datadef printself(*arg):# for item in arg:# print item.dataprint("printself: ", arg)staticmethoddef smethod(*arg):prin…

python3元类简介(metaclass)

在Python中可以用内置函数type查看对象的类型&#xff0c;isinstance查看某个对象是某个类实例&#xff0c;通过type可以实现动态类&#xff0c;以及通过metaclass实现动态类 type()与isinstance()判断对象类型 Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v…

linux ps 命令使用

Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照&#xff0c;就是执行ps命令的那个时刻的那些进程&#xff0c;如果想要动态的显示进程信息&#xff0c;就可以使用top命令。 linux上进程有5种状态 ps命令使…

UML序列图总结

序列图主要用于展示对象之间交互的顺序。 序列图将交互关系表示为一个二维图。纵向是时间轴&#xff0c;时间沿竖线向下延伸。横向轴代表了在协作中各独立对象的类元角色。类元角色用生命线表示。当对象存在时&#xff0c;角色用一条虚线表示&#xff0c;当对象的过程处于激活…

UML用例图总结

用例图主要用来描述 用户、需求、系统功能单元 之间的关系。它展示了一个外部用户能够观察到的系统功能模型图。 【用途】&#xff1a;帮助开发团队以一种可视化的方式理解系统的功能需求。 用例图所包含的元素如下&#xff1a; 1. 参与者(Actor) 表示与您的应用程序或…

动态规划--换零钱

题目描述 想兑换100元钱&#xff0c;有1,2,5,10四种钱&#xff0c;问总共有多少兑换方法 递归解法 #include<iostream> using namespace std; const int N 100; int dimes[] {1, 2, 5, 10}; int arr[N1] {1}; int coinExchangeRecursion(int n, int m) //递归…

学生表 课程表 成绩表 教师表常用SQL语句

学生表 课程表 成绩表 教师表 50个常用sql语句 建表 Student(S#,Sname,Sage,Ssex) 学生表 Course(C#,Cname,T#) 课程表 SC(S#,C#,score) 成绩表 Teacher(T#,Tname) 教师表 ---- If database exists the same name datatable deletes it. IF EXISTS(SELECT TABLE_NAME FRO…

Linux网络编程常见面试题

概述 TCP和UDP是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议。 TCP&#xff1a;传输控制协议&#xff0c;一种面向连接的协议&#xff0c;给用户进程提供可靠的全双工的字节流&#xff0c;TCP套接口是字节流套接口(stream socket)的一种。UDP&#xff1a;用户…

sizeof与offsetof有关的结构体详解

sizeof与offsetof在程序中经常遇到&#xff0c;但在面试中其应用使得许多小伙伴吃闭门羹&#xff0c;被面试官问得哑口无言。接下来对两者的应用做详细介绍。 关于sizeof 定义 sizeof乃C/C中的一个操作符(operator), 简单的说其作用就是返回一个对象或者类型所占的内存字节数…

linux线程间同步(1)读写锁

读写锁比mutex有更高的适用性&#xff0c;可以多个线程同时占用读模式的读写锁&#xff0c;但是只能一个线程占用写模式的读写锁。 1. 当读写锁是写加锁状态时&#xff0c;在这个锁被解锁之前&#xff0c;所有试图对这个锁加锁的线程都会被阻塞&#xff1b; 2. 当读写锁在读加…

linux线程间同步(1)互斥锁与条件变量

线程的最大特点是资源的共享性&#xff0c;但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步&#xff0c;最常用的是互斥锁、条件变量和信号量以及读写锁。 互斥锁(mutex) 互斥锁&#xff0c;是一种信号量&#xff0c;常用来防止两个进程或线…

Linux下压缩包生成与解压命令以及进度

不同后缀压缩包的打包与加压命令 .tar 解包&#xff1a;tar xvf FileName.tar打包&#xff1a;tar cvf FileName.tar DirName .gz 解压1&#xff1a;gunzip FileName.gz解压2&#xff1a;gzip -d FileName.gz压缩&#xff1a;gzip FileName .tar.gz 和 .tgz 解压&#xff1a;…

经典面试题

谷歌面试题&#xff1a;1024! 末尾有多少个0&#xff1f; 末尾0的个数取决于乘法中因子2和5的个数。显然乘法中因子2的个数大于5的个数&#xff0c;所以我们只需统计因子5的个数。 是5的倍数的数有&#xff1a; 1024 / 5 204个;对于25,50这些数据统计一次,但实际的是包含两个…

共享内存:mmap函数实现

内存映射的应用: 以页面为单位,将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;将特殊文件进行匿名内存映射&#xff0c;可以为关联进程提供共享内存空间;为无关联的进程提供共享内存空间&#xff0c;一般也是将…

MSYS2开发环境搭建

MSYS2开发环境搭建 软件安装 下载msys2-x86_64软件包 https://www.msys2.org/&#xff0c;双击安装到某根目录下&#xff0c;比如D:\msys64。 pacman是MSYS2自带的软件管理工具&#xff1a; 可通过修改msys64\etc\pacman.d下的三个文件修改软件源&#xff0c;可供选择的源有…

设置python路径

在python开发应用&#xff0c;我们多数是通过pip、easy_install等工具将需要的python安装到自己机子上就可以应用了&#xff0c;但是我们完成开发给用户使用时&#xff0c;程序运行环境就是一个问题。当然&#xff0c;你可以要求客户按照你的方法安装依赖的库&#xff0c;这种方…

linux动态库查找路径以及依赖关系梳理

编译时与运行时库的路径 linux下&#xff0c;编译时与运行时库的搜索路径是不同的 运行时动态库的路径搜索顺序 LD_PRELOAD环境变量&#xff0c;一般用于hack 编译目标代码时指定的动态库搜索路径(指的是用 -wl,rpath 或-R选项而不是-L)&#xff0c;readelf -d命令可以查看编…

eclipse--android开发环境搭建教程

引言 在windows安装Android的开发环境不简单也说不上算复杂&#xff0c;但由于国内无法正常访问google给android开发环境搭建带来不小的麻烦。现将本人搭建过程记录如下&#xff0c;希望会对投身android开发的小伙伴有所帮助。 android开发环境部署过程 安装JDK环境 下载安装…

pip工具使用总结以及常用库PIL、freetype的安装

pip工具安装使用 pip为python库软件管理工具pip docs 安装 wget https://bootstrap.pypa.io/ez_setup.py -O - | python 安装setuptools https://pypi.python.org/pypi/setuptoolswget https://bootstrap.pypa.io/get-pip.py -O - | python 安装pip工具 ttps://pypi.python.…