学习路径
/
Python
Python
/
教学大纲
/
解包与多重赋值
解包

解包与多重赋值 属于 Python

3 个练习

关于 解包与多重赋值

解包是指通过迭代,从集合(例如list、tuple或dict)中取出各个元素的操作。 解包得到的值随后可以在同一条语句中赋给变量。 这种行为最常见的例子是for item in list,其中item会在整个迭代过程中依次取到list每个元素的值。

多重赋值是指在一条语句中把解包出来的值赋给多个变量。 它能让代码更简洁、更易读;做法是用逗号分隔要赋值的变量,例如first, second, third = (1,2,3)或for index, item in enumerate(iterable)。

特殊运算符*和**经常用在解包的场景中。 *可以把多个list/tuple合并成一个list/tuple,做法是把它们各自_解包_进一个新的公共list/tuple。 **可以把多个字典合并成一个字典,做法是把它们各自_解包_进一个新的公共dict。

当*运算符不作用于任何集合时,它会把若干值_打包_进一个list。 多重赋值中经常这样用:把所有没有单独赋值的“剩余”元素归到同一个变量里。

在 Python 中,调用或定义接受任意数量位置实参或关键字实参的函数时,也常常利用这种解包/打包行为。 你经常会看到这样的“特殊”形参定义:def some_function(*args, **kwargs),以及这样的“特殊”实参用法:some_function(*some_tuple, **some_dict)。

Caution

不要把*<variable_name>和**<variable_name>与*和**混为一谈。*和**分别用于乘法和幂运算,而*<variable_name>和**<variable_name>则是打包和解包运算符。

多重赋值

在多重赋值中,赋值运算符(=)左边的变量数量必须与右边的值数量一致。 分隔多个值时,使用逗号,:

>>> a, b = 1, 2
>>> a
1

如果多重赋值中变量的数量与给出的值不匹配,就会抛出ValueError:

>>> x, y, z = 1, 2

ValueError: too many values to unpack (expected 3, got 2)

多重赋值并不局限于一种数据类型:

>>> x, y, z = 1, "Hello", True
>>> x
1

>>> y
'Hello'

>>> z
True

多重赋值可以用来交换lists中的元素。 这种做法在排序算法中很常见。 例如:

>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]

由于tuples是不可变的,所以无法交换tuple中的元素。

解包

Note

下面的例子使用的是lists,但同样的概念也适用于tuples。

在 Python 中,可以把list/tuple/dict的元素解包到多个不同的变量中。 由于lists/tuples中的值有固定的顺序,它们会按同样的顺序解包到变量里:

>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"

如果有些值用不到,可以用_标记它们:

>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"

深层解包

从list或tuple内部的list/tuple中解包并赋值(也叫嵌套数组/元组),其方式和浅层解包一样,但往往需要用限定符说明值的上下文或位置:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"

>>> d
"potato"

你也可以只对嵌套list/tuple的一部分做深层解包:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]

>>> c
"carrot"

如果解包时变量的位置不对,或者值的数量不对,就会得到ValueError:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables

ValueError: too many values to unpack (expected 1)

用*解包数组/元组

解包list/tuple时,可以用*运算符捕获“剩余”的值。 这比切片list/tuple更清晰(在某些情况下切片可读性更差)。 例如,我们可以取出第一个元素,把剩下的值打包成一个不含第一个元素的新list:

>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"

>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]

我们也可以取出list开头和结尾的值,同时把中间的所有值归为一组:

>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"

>>> middle
["banana", "cherry", "orange", "kiwi"]

在深层解包中同样可以使用*:

>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"

>>> rest
["banana", "melon"]

解包字典

解包字典与解包list/tuple稍有不同。 对字典进行迭代时,默认得到的是键。 所以解包dict时,只能解出键,不能解出值:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"

如果想解出值,可以使用<dict>.values()方法:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6

如果键和值都需要,就使用<dict>.items()方法。 <dict>.items()会生成一个包含键值对的可迭代视图。 这些键值对可以解包到一个tuple中:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)

打包

打包是指把多个值归到一个list中并赋给一个变量的能力。 当你想要_解包_一些值、做些修改,再把结果_打包_回一个变量时,这很有用。 它还可以对 2 个或更多lists/tuples/dicts执行合并。

用*打包数组/元组

打包list/tuple可以使用*运算符。 它会把所有值打包进一个list/tuple。

>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]

# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits

# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")

# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma. 
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']

# A list literal can be used instead, but might not be as readable.
>>> [*combined_fruits_too] = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']

想进一步了解*和**的用法,可以看看 PEP 3132 和 PEP 448。

用**打包字典

打包字典使用**运算符。 它会把一个字典中的所有键-值对打包进另一个字典,或者把两个字典合并起来。

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}

# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}

# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}

在函数中使用*和**

用函数形参打包

定义接受任意数量实参的函数时,可以在函数定义中使用*args或**kwargs。 *args用来打包任意数量的位置(非关键字)实参,**kwargs用来打包任意数量的关键字实参。

*args的用法:

# This function is defined to take any number of positional arguments

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

# Arguments given to the function are packed into a tuple

>>> my_function(1, 2, 3)
(1, 2, 3)

>>> my_function("Hello")
("Hello")

>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")

**kwargs的用法:

# This function is defined to take any number of keyword arguments

>>> def my_function(**kwargs):
...   print(kwargs)

# Arguments given to the function are packed into a dictionary

>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}

*args和**kwargs也可以相互配合使用:

>>> def my_function(*args, **kwargs):
...   print(sum(args))
...   for key, value in kwargs.items():
...       print(str(key) + " = " + str(value))

>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3

你也可以在*args前面写形参,以支持特定的位置实参。 这时各个关键字实参必须出现在**kwargs之前。

Caution

实参必须按这样的结构排列:

def my_function(<positional_args>, *args, <key-word_args>, **kwargs)

如果不按这个顺序写,就会报错。

>>> def my_function(a, b, *args):
...   print(a)
...   print(b)
...   print(args)

>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)

实参顺序写错会导致报错:

>>> def my_function(*args, a, b):
...   print(args)

>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
  File "c:\something.py", line 3, in <module>
    my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'

在函数调用中解包

可以用*把一个装着实参的list/tuple解包到函数调用里。 对于不接受iterable的函数,这非常有用:

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

numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1

另一个常见的用法是配合内置的zip()使用*解包。 zip()函数接收多个可迭代对象,返回一个由tuples组成的list,其中把来自各个iterable的值分好组:

>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]
通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 解包与多重赋值

练习已锁定

再解锁 3 个练习即可练习 解包与多重赋值