python 理解 ‘*‘、‘*args‘、‘**‘ 和 ‘**kwargs‘


当我开始学习Python的,我很迷茫关于什么args,kwargs,*和**做。我觉得有很多像我一样有这种困惑和问题的人。通过这篇文章,我打算减少(希望我能消除)这种混乱。

在这篇文章中,我将使用 ipython,我建议你也尝试在 ipython 上使用所有东西。一路上我们会故意犯一些错误,以便更好地理解这个话题。

1、了解 * 在函数调用中的作用

让我们定义一个函数“fun”,它接受三个位置参数:

  In [5]: def fun(a, b, c):
     ...:     print a, b, c

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

In [7]: fun(1,2,3)
1 2 3                     #Output

因此,通过传递三个位置参数来调用此函数,打印传递给该函数的三个参数。

让我们创建一个包含三个整数值的列表。

In [8]: l = [1,2,3]

让我们*现在使用。

In [9]: fun(*l)
1 2 3                     #Output

*做了什么?

它将列表中的值解包l为位置参数。然后将解包后的值作为位置参数传递给函数“fun”。

因此,将 list 中的值解包并将其更改为位置参数意味着 fun(*l)fun(1,2,3)等效。请记住,l=[1,2,3]。让我们尝试使用 的其他一些值l

In [10]: l=[5,7,9]

In [11]: fun(*l)
5 7 9                     #Output

现在让我们犯一些错误。让我们在“l”中放入四个值:

In [12]: l=[3,5,6,9]

现在,尝试调用函数“fun”:

In [13]: fun(*l)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/akshar/branding_git/netconference/<ipython console> in <module>()

TypeError: fun() takes exactly 3 arguments (4 given)

因此,在我们的最后一条语句中,fun(*l)我们没有得到正确的输出并且引发了 TypeError。看到错误,它说“TypeError: fun() 需要 3 个参数(给定4 个)”。

为什么会这样?

列表 ‘l’ 包含四个值。因此,当我们尝试调用fun(*l)l被解包,以便它的值可以作为位置参数发送。但是,l里面有四个值。所以,fun(\*l)fun(3,5,6,9)等效。但是,fun的定义只采用三个位置参数,因此我们得到了这个错误。类似地,您可以对列表 'l' 中的两个值执行相同的步骤并注意错误。

In [14]: l=[5,6]

In [15]: fun(*l)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/akshar/branding_git/netconference/<ipython console> in <module>()

TypeError: fun() takes exactly 3 arguments (2 given)

让我们给定*l和一个位置参数。

In [16]: fun(1, *l)
1 5 6                  #Output.

在这里,我们给出了一个位置参数,即 1 和两个值,即 5 和 6 从“l”中解包出来,因此 1,5 和 6 被传递给 ‘fun’。

希望您能够了解*在函数调用中使用时的作用。

了解*args函数定义中的含义。
现在让我们更改函数定义。

In [18]: def fun(*args):
   ....:     print args

使用一个位置参数调用此函数:

In [19]: fun(1)
(1,)                  #Output

现在使用两个位置参数或您希望的任意数量的位置参数调用此函数:

In [20]: fun(1,2,3)
(1, 2, 3)

2、*args在函数定义中做什么?

它接收一个包含超出形式参数列表的位置参数的元组。所以,args是一个元组。不要担心我们解释中的“形参列表”部分,接下来的几个例子会很清楚。在我们打印“args”的最后一个示例中,它打印了一个元组,其中包含我们在调用函数时传递的所有值。

让我们把*args和一些“形参列表”混合使用。请注意,在我们的最后一个示例中,我们没有任何形参列表。让我们重新定义我们的函数。

In [21]: def fun(a, *args):
   ....:     print "a is ", a
   ....:     print "args is ", args

在这个函数定义中,参数“a”构成了“形式参数列表”。让我们用四个位置参数来称呼“乐趣”。

In [22]: fun(1, 2, 3, 4)
a is  1                                #Output
args is  (2, 3, 4)

所以,我们可以看到 ‘a’ 被赋值为 1,这是第一个位置参数。在“a”后只定义了一个参数*args。因此,“args”收到一个元组,其中包含形参列表之外的位置参数。

因此,args 接收到 2、3 和 4 作为元组。

我们也可以只用一个位置参数来调用“fun”:

In [23]: fun(1)
a is  1                                #Output
args is  ()                            #args received an empty tuple

Here, we passed only one argument to the function which was assigned to the formal parameter ‘a’. So, ‘args’ received an empty tuple as can be seen from the output.

在这里,我们只给函数传递了一个参数,这个函数被赋值给形参‘ a’。因此,从输出中可以看到,“ args”接收到一个空元组。

在我们有了“ args”之后,我们可以提取这些值并做任何我们想做的事情。让我们重新定义“ fun”。

In [24]: 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”。

In [25]: fun(1,5,6,7)
1                                                                         #Output
args can receive a tuple of any number of arguments. Let's print all that.
5
6
7

因为“ args”是一个元组,所以我们可以遍历它。

现在,让我们考虑一个案例,我们使用我们在这里看到的任何东西。这里我们需要使用两个函数。第一个函数必须接受任意数量的参数,它必须计算除第一个参数以外的所有参数的和。此外,这个函数必须使用另一个函数来求和。这里的目标是了解如何在一个函数中获得可变数量的参数,并将这些参数传递给另一个函数。

让我们先写一个函数来求和,也就是第二个函数。对于我们的例子,这个函数将用于我们尚未编写的第一个函数。

In [26]: def calculate_sum(*args):
   ....:     return sum(args)
   ....:

这里我们使用‘ sum’。函数‘ sum’是一个内置函数,它接受一个元组或一个列表,并返回元组中所有元素的和。从我们的函数定义中可以看到,“ args”将接收一个包含传递给该函数的所有位置参数的 tuple。因此,“ args”是一个元组,可以直接用作函数“ sum”的参数。让我们编写另一个函数,它接受任意数量的参数,并使用以前的函数计算除第一个参数以外的所有参数的和。

In [29]: def ignore_firstargs_calculate_sum(a, *iargs):
   ....:     required_sum = calculate_sum(*iargs)
   ....:     print "sum is", required_sum
   ....:
   ....:

我们可以向这个函数传递任意数量的参数。第一个参数由 a 接收,a 是一个形式参数。所有其他的参数都会被“ iargs”作为一个元组来接收。根据我们正在考虑的情况,我们希望计算除第一个参数以外的所有参数的和。因此,当 a 接收到第一个参数时,我们留下 a。‘ iargs’是包含除第一个参数以外的所有参数的元组。我们将利用函数‘ calculate _ sum’。但是‘ calculate _ sum’期望发送给它的位置参数数量,它将在‘ args’中以元组的形式接收这些参数。所以,在函数‘ ignore _ firstargs calculate _ sum’中,我们需要解压缩 iargs,因为它是一个 tuple,然后发送解包的位置参数来‘ calculate _ sum’。记住,我们用 * 来解包列表/元组。

因此,我们写下 required _ sum = calculate _ sum (* iargs)

我们不能写下 required _ sum = calculate _ sum (iargs) ,因为在发送 calculate _ sum 之前,我们需要解包 tuple iargs 中的值。不使用 * 将不会解包这些值,则不会发生期望的动作。

In [34]: ignore_firstargs_calculate_sum(3,1,2,6)
sum is 9                          #Output

输出是除第一个参数之外所有参数的和。

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

让我们首先考虑一个简单的例子,让我们定义一个有三个参数的函数。

In [35]: def fun(a, b, c):
   ....:     print a, b, c
   ....:
   ....: Let's call this function in various ways.

In [36]: fun(1,2,3)
1 2 3                            #Output

In [37]: fun(1, b=4, c=6)
1 4 6                            #Output

使用”**”调用函数,需要一个字典 。注意:在函数调用中使用 * 时,我们需要一个 list/tuple。为了在函数调用中使用 * * ,我们需要一个字典。

In [38]: d={'b':5, 'c':7}

让我们在函数调用中使用 * * 来调用 fun:

In [39]: fun(1, **d)
1 5 7

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

它解包了字典,并将字典中的项目作为关键字参数传递给函数。所以fun(1, **d)等同于fun(1, b=5, c=7)

让我们尝试更多示例以更好地理解它。

In [40]: d={'c':3}

In [42]: fun(1,2,**d)           #This is equivalent to fun(1,2,c=3)
1 2 3

In [43]: d={'a':7,'b':8,'c':9}

In [44]: fun(**d)
7 8 9                           #Output

现在让我们犯一些错误:

In [45]: d={'a':1, 'b':2, 'c':3, 'd':4}

In [46]: fun(**d)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/akshar/branding_git/netconference/<ipython console> in <module>()

TypeError: fun() got an unexpected keyword argument 'd'

最后一条语句相当于fun(a=1, b=2, c=3, d=4). 但是,fun预计只有三个参数,因此我们得到了这个错误。

In [47]: d={'a':1, 'b':5, 'd':9}

In [48]: fun(**d)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/akshar/branding_git/netconference/<ipython console> in <module>()

TypeError: fun() got an unexpected keyword argument 'd'

最后一条语句等价于 fun(a=1, b=5, d=9)。虽然它传递了三个参数,这是“fun”需要的参数数量,但“fun”的参数列表中没有“d”。但是,“d”作为关键字参数传递,因此我们得到了这个错误。

因此,**解包字典,即字典中的键值对作为关键字参数,并将这些作为关键字参数发送给被调用的函数。*解包一个列表/元组,即列表中的值作为位置参数,这些值作为位置参数发送到被调用的函数。

4、**kwargs在函数定义中的含义

让我们重新定义我们的函数“fun”。

理解在函数定义中的**kwargs
让我们重新定义我们的函数 fun。

In [49]: def fun(a, **kwargs):
   ....:     print a, kwargs
   ....:
   ....:

因此,这个函数只能接受一个位置参数,因为形式参数列表只包含一个变量’a’。但是使用 * * kwargs,它可以接受任意数量的关键字参数。让我们看一些例子。

In [50]: fun(1, b=4, c=5)
1 {'c': 5, 'b': 4}                #Output

In [51]: fun(2, b=6, c=7, d=8)
2 {'c': 7, 'b': 6, 'd': 8}        #Output

#当**kwargs用在函数定义中是什么意思?

随着**kwargs在函数定义中,kwargs将接收一个字典,其中包含形参列表之外的所有关键字参数。记住kwargs是一个字典。

在我们前面的两个例子中,当我们打印时kwargs,它打印了一个字典,其中包含形参列表之外的所有关键字参数。

让我们再次重新定义我们的函数:

In [54]: 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":

In [55]: fun(1, b=3, c=5)
a is 1
We expect kwargs 'b' and 'c' in this function
b is 3
c is 5

让我们现在犯一些错误:

In [56]: fun(1, b=3, d=5)
a is 1
We expect kwargs 'b' and 'c' in this function
b is 3
c is---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)

/home/akshar/branding_git/netconference/<ipython console> in <module>()

/home/akshar/branding_git/netconference/<ipython console> in fun(a, **kwargs)

KeyError: 'c'

我们可以调用函数。第一个位置参数被打印出来。已打印关键字参数“b”。但我们传递的另一个关键字参数是“d”。因为函数需要关键字参数“c”,并试图从字典“kwargs”访问它。但是由于我们没有传递任何关键字参数’c’,我们得到了这个错误。如果我们在函数调用中添加一个关键字参数’c’,我们就不会再得到错误了。

In [57]: fun(1, b=3, d=5, c=7)
a is 1
We expect kwargs 'b' and 'c' in this function
b is 3
c is 7

由于函数参数列表中有**kwargs,因此我们可以传递任意数量的关键字参数。我们传递了d,但没有在函数中使用它。

让我们再犯一个错误:

In [58]: fun(1, {'b':2, 'c':3})
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/akshar/branding_git/netconference/<ipython console> in <module>()

TypeError: fun() takes exactly 1 argument (2 given)

正如错误所示,函数“fun”只需要一个位置参数,却得到了两个。因此,尽管“kwargs”将关键字参数作为字典接收,但不能将字典作为位置参数传递给“kwargs”。尽管你可以做一些事情,比如:

In [59]: fun(1, **{'b':2, 'c':3})
a is 1
We expect kwargs 'b' and 'c' in this function
b is 2
c is 3

在字典前面使用**解包字典, 并将字典中的项作为关键字参数传递。

5、给出一个实例说明我们为什么要用*args, **kwargs

每当我们继承一个类并重写继承类的一些方法时,我们应该使用*args**kwargs,并将接收到的位置和关键字参数传递给超类(父类)方法。用一个例子可以更好地理解。

In [4]: 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:
   ...:             #Update an existing record
   ...:             print "Updated an existing record"
   ...:         if force_insert:
   ...:             #Create a new record
   ...:             print "Created a new record"
   ...:

我们定义了一个类。我们可以创建这个类的对象,这个类的对象有一个方法“save()”。假设类的对象可以通过save()方法保存到数据库中。根据我们传递给 save() 方法的参数,确定是需要在数据库中创建新记录,还是需要更新现有记录。

构建一个新类,类有“Model”的动作,但只有满足一些条件才保存这个类的对象。因此,让我们继承 ‘Model’ 并重写 ‘Model’ 的 ‘save()’。

In [6]: 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实体并保存:

In [7]: c=ChildModel('abcd')

因此,我们创建了一个名称为 =‘abcd’ 的 ChildModel 实例。

In [9]: c.save(force_insert=True)
Created a new record       #Output

在这里,我们向对象的 save() 传递了一个关键字参数。我们把save()叫做子类save()。它接受一个包含关键字参数的字典kwargs。然后用**解包这个字典作为关键字参数,然后将它传递给父类save()。所以,父类save()得到了一个关键字参数force_insert并完成相应的操作。

让我们尝试传递另一个关键字参数。

In [10]: c.save(force_update=True)
Updated an existing record      #Output

这就是全部。希望你喜欢这篇文章。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值