如何在Python中使用解包运算符(*,**)?

python是最常用的编程语言。今天你将学习使用python的核心特性之一,即解包(unpacking)。

您可能在其他人的代码中看到过*和**,甚至可能在不知道其目的的情况下使用它们。我们将逐步介绍解包的概念,以及如何使用它编写更具python风格的代码。

在阅读本教程时,以下概念可能对您有用:

  • 可迭代对象:任何可以由for循环迭代的序列,如集合、列表、元组和字典
  • 可调用对象:可以使用双括号()调用的python对象,例如myfunction()
  • shell:交互式运行环境,可以运行python代码。我们可以在终端中运行“python”来调用它
  • 变量:符号名称,存储对象并具有预留的内存位置。

让我们从最常见的困惑开始:python中的星号也是算术运算符。一个星号(*)用于乘法,而两个星号(**)表示乘方。

我们可以通过打开python shell并键入以下内容来证明这一点:

>>> 3*3
9
>>> 3**3
27

注意:您需要安装python 3才能跟随本教程。如果您尚未安装,请查看我们的python安装指南

如您所见,我们在第一个数字之后和第二个数字之前使用了星号。当您看到这个符号时,它表示我们正在使用算术运算符。

另一方面,我们使用星号(*, **)在可迭代对象之前解包它 – 例如:

>>> *range(1, 6),
(1, 2, 3, 4, 5)
>>> {**{'vanilla':3, 'chocolate':2}, 'strawberry':2}
{'vanilla': 3, 'chocolate': 2, 'strawberry': 2}

如果您不理解,不用担心,这只是python中解包的一个前导部分。所以,请继续阅读整个教程!

什么是解包?

解包是获取东西的过程 – 如列表、元组和字典等可迭代对象。将其视为打开盒子并取出不同物品(如电缆、耳机或usb)。

在python中解包类似于在现实生活中打开盒子。

让我们将这个例子翻译成代码,以更好地理解:

>>> mybox = ['cables', 'headphones', 'usb']
>>> item1, item2, item3 = mybox

如您所见,我们将mybox列表中的三个物品分配给了三个变量item1,item2,item3。这种变量赋值是python中解包的基本概念。

如果尝试获取每个物品的值,您会注意到item1指向“cables”,item2指向“headphones”,依此类推。

>>> item1
'cables'
>>> item2
'headphones'
>>> item3
'usb'

到目前为止,这段代码似乎一切正常,但是如果我们希望解包一个包含更多元素的列表 – 仍然保持相同数量的分配变量呢?

>>> newbox = ['cables', 'headphones', 'usb', 'mouse']
>>> item1, item2, item3 = newbox
traceback (most recent call last):
  file "", line 1, in 
valueerror: too many values to unpack (expected 3)

可能你期望得到这种类型的错误。在本质上,我们将4个列表项分配给3个变量,python如何分配正确的值呢?

事实上,它并不会,这是因为我们得到了一个“valueerror”错误,错误消息是“too many values to unpack”。这是因为我们在左边设置了3个变量,而在右边有4个值(对应于newbox列表)。

如果你尝试使用更多的变量来进行类似的处理,但是值不够解包,你将得到另一个“valueerror”错误,只是错误消息略有不同:

>>> lastbox = ['cables', 'headphones']
>>> item1, item2, item3 = lastbox
traceback (most recent call last):
  file "", line 1, in 
valueerror: not enough values to unpack (expected 3, got 2)

注意:我们一直在使用列表,但是你可以在任何可迭代对象(列表、集合、元组、字典)中使用这种解包形式。

那么我们如何克服这种情况呢?有没有办法将可迭代对象的所有项解包给一对变量而不会出现任何错误呢?

当然有,它被称为解包运算符或星号运算符(*,**)。让我们看看如何在python中使用它。

使用 * 运算符解包列表

星号运算符(*)用于解包尚未分配的可迭代对象的所有值。

假设你想要获取列表的第一个元素和最后一个元素而不使用索引,我们可以使用星号运算符来实现:

>>> first, *unused, last = [1, 2, 3, 5, 7]
>>> first
1
>>> last
7
>>> unused
[2, 3, 5]

正如你所看到的,我们使用星号运算符得到了所有未使用的值。丢弃值的首选方法是使用下划线变量(_),有时被用作“哑变量”。

>>> first, *_, last = [1, 2, 3, 5, 7]
>>> _
[2, 3, 5]

即使列表只有两个元素,我们仍然可以使用这个技巧:

>>> first, *_, last = [1, 2]
>>> first
1
>>> last
2
>>> _
[]

在这种情况下,下划线变量(哑变量)存储一个空列表,所以它们周围的其他两个变量可以访问列表中可用的值。

常见故障排除

我们可以解包可迭代对象的唯一元素。例如,你可能会遇到这样的情况:

>>> *string = 'pythonisthebest'

然而,上述代码将返回一个syntaxerror

>>> *string = 'pythonisthebest'
  file "", line 1
syntaxerror: starred assignment target must be in a list or tuple

这是因为根据pep规范:

简单赋值左边的元组(或列表)

如果我们想将可迭代对象的所有值解包到一个变量中,我们必须设置一个元组,因此只需要添加一个逗号即可:

>>> *string, = 'pythonisthebest'
>>> string
['p', 'y', 't', 'h', 'o', 'n', 'i', 's', 't', 'h', 'e', 'b', 'e', 's', 't']

另一个例子是使用range函数,它返回一个数字序列。

>>> *numbers, = range(5)
>>> numbers
[0, 1, 2, 3, 4]

既然你知道如何用星号解包列表和元组,现在是时候解包字典了。

如何使用**操作符解包字典

单个星号用于解包列表和元组,双星号(**)用于解包字典。

不幸的是,我们不能像解包元组和列表那样将字典解包为单个变量。这意味着以下代码会报错:

>>> **greetings, = {'hello': 'hello', 'bye':'bye'} 
...
syntaxerror: invalid syntax

然而,我们可以在可调用对象和其他字典中使用**操作符。例如,如果我们想要创建一个由其他字典组成的合并字典,我们可以使用以下代码:

>>> food = {'fish':3, 'meat':5, 'pasta':9} 
>>> colors = {'red': 'intensity', 'yellow':'happiness'}
>>> merged_dict = {**food, **colors}
>>> merged_dict
{'fish': 3, 'meat': 5, 'pasta': 9, 'red': 'intensity', 'yellow': 'happiness'}

这是创建复合字典的一种简洁的方法,但这并不是python中解包的主要方法。

让我们看看如何在可调用对象中使用解包。

在函数中的打包和解包:args和kwargs

您可能以前在类或函数中看到过args和kwargs的实现。让我们看看为什么我们需要在可调用对象中使用它们。

使用*操作符(args)进行打包

假设我们有一个计算两个数字乘积的函数。

>>> def product(n1, n2):
...     return n1 * n2
... 
>>> numbers = [12, 1]
>>> product(*numbers)
12

正如您所看到的,我们正在解包列表numbers到函数中,所以实际上我们运行的是以下代码:

>>> product(12, 1)
12

到目前为止,一切都很正常,但如果我们想传递一个更长的列表会怎么样?这肯定会引发错误,因为函数接收到的参数比它能处理的参数多。

>>> numbers = [12, 1, 3, 4]
>>> product(*numbers)
...
typeerror: product() takes 2 positional arguments but 4 were given

我们可以通过直接在函数中打包列表来解决所有这些问题,这样在函数内部创建一个可迭代对象,并允许我们向函数传递任意数量的参数。

>>> def product(*args):
...     result = 1
...     for i in args:
...             result *= i
...     return result
...
>>> product(*numbers)
144

在这里,我们将参数args视为一个可迭代对象,遍历其元素并返回所有数字的乘积。请注意,结果的起始数字必须为1,因为如果我们从零开始,函数将始终返回零。

注意:args只是一种约定,您可以使用任何其他参数名称

我们还可以像内置的打印函数一样,不使用列表而直接传递任意数量的数字给函数。

>>> product(5, 5, 5)
125
>>> print(5, 5, 5)
5 5 5

最后,让我们看一下函数的args的对象类型。

如上所示的代码,args的类型将始终为元组,并且其中的内容将是传递给函数的所有非关键字参数。

使用**运算符(kwargs)进行打包

正如前面所见,**运算符仅用于字典。这意味着我们可以使用此运算符将键值对作为参数传递给函数。

让我们创建一个名为make_person的函数,它接收一个位置参数“name”,以及一个未定义数量的关键字参数。

如您所见,**kwargs语句将所有关键字参数转换为字典,我们可以在函数内部进行迭代。

注意:kwargs只是一个约定俗成的命名,您可以用任何您想要的名称命名此参数

我们可以像处理args一样检查kwargs的类型:

kwargs内部变量始终是一个字典,它存储传递给函数的键值对。

最后,让我们在同一个函数中使用args和kwargs:

解包运算符在日常任务中非常有用,现在您知道如何在单个语句和函数参数中使用它们。

在本教程中,您学到了:

– 您可以在元组和列表中使用*,并在字典中使用**
– 您可以在函数和类构造函数中使用解包运算符
– 使用args将非关键字参数传递给函数
– 使用kwargs将关键字参数传递给函数。

接下来,您可以了解如何使用python的不等运算符。

类似文章