Skip to content

Python打包和解包

解包Unpacking

Python 允许变量的元组(或列表)出现在赋值操作的左侧。

元组中的每个变量都可以从赋值右侧的可迭代对象(iterable)中接收一个值(或者更多,如果我们使用 * 运算符)。 Python 中的解包是指一种操作,该操作包括在单个赋值语句中将可迭代的值分配给变量的元组(或列表)。

在 Python 中,可以在赋值运算符=的左侧放置一个变量元组,在右侧放置一个值元组。 右边的值将根据它们在元组中的位置自动分配给左边的变量。 这在 Python 中通常称为元组解包。

如下示例:

>>> (a, b, c) = (1, 2, 3)
>>> a
1
>>> b
2
>>> c
3
>>> birthday = ('April', 5, 2001)
>>> month, day, year = birthday
>>> month
'April'
>>> day
5
>>> year
2001

元组解包功能在 Python 中可以扩展为适用于任何可迭代对象。 唯一的要求是可迭代的接收元组(或列表)中的每个变量恰好对应可迭代对象的一个元素(item)。

下面的示例介绍了 Python 中可迭代解包的工作原理:

>>> # Unpackage strings
>>> a, b, c = '123'
>>> a
'1'
>>> b
'2'
>>> c
'3'
>>> # Unpacking lists
>>> a, b, c = [1, 2, 3]
>>> a
1
>>> b
2
>>> c
3
>>> # Unpacking generators
>>> gen = (i ** 2 for i in range(3))
>>> a, b, c = gen
>>> a
0
>>> b
1
>>> c
4
>>> # Upacking dictionaries (keys, values, and items)
>>> my_dict = {'one': 1, 'two': 2, 'three': 3}
>>> a, b, c = my_dict
>>> a
'one'
>>> b
'two'
>>> c
'three'
>>> a, b, c = my_dict.values()
>>> a
1
>>> b
2
>>> c
3
>>> a, b, c = my_dict.items()
>>> a
('one', 1)
>>> b
('two', 2)
>>> c
('three', 3)
>>> # Use a tuple on the right side of assignment statement
>>> [a, b, c] = 1, 2, 3
>>> a
1
>>> b
2
>>> c
3
>>> # Use range() iterator
>>> x, y, z = range(3)
>>> x
0
>>> y
1
>>> z
2

打包Packing

打包可以理解为使用可迭代解包运算符在单个变量中收集多个值。在这种情况下, * 运算符被称为元组(或可迭代)解包运算符。 它扩展了解包功能,允许在单个变量中收集或打包多个值。

在以下示例中可以看到 * 运算符将元组值打包到单个变量中:

>>> # The right side is a tuple, the left side is a list
>>> *a, = 1, 2
>>> a
[1, 2]
>>> type(a)
<class 'list'>

在上面的代码中,赋值的左侧必须是元组(或列表),这就是使用尾随逗号的原因。这个元组可以包含所需要的尽可能多的变量,但是,它只能包含一个星号表达式(starred expression)。

>>> # Packing trailing values
>>> a, *b = 1, 2, 3
>>> a
1
>>> b
[2, 3]
>>> type(a)
<class 'int'>
>>> type(b)
<class 'list'>
>>> 
>>> *a, b, c = 1, 2, 3
>>> a
[1]
>>> b
2
>>> c
3
>>> *a, b, c, d, e = 1, 2, 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected at least 4, got 3)
>>> *a, b, c, d = 1, 2, 3
>>> a
[]
>>> b
1
>>> c
2
>>> d
3
>>> 
>>> seq = [1, 2, 3, 4]
>>> first, *body, last = seq
>>> first, body, last
(1, [2, 3], 4)
>>> first, body, *last = seq
>>> first, body, last
(1, 2, [3, 4])
>>> 
>>> ran = range(10)
>>> *r, = ran
>>> r
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

下面是一些打包和解包的例子。

>>> employee = ['John Doe', '40', 'Software Engineer']
>>> name = employee[0]
>>> age = employee[1]
>>> job = employee[2]
>>> name
'John Doe'
>>> age
'40'
>>> job
'Software Engineer'
>>> 
>>> name, age, job = ['John Doe', '40', 'Software Engineer']
>>> name
'John Doe'
>>> age
'40'
>>> job
'Software Engineer'
>>> 
>>> a = 100
>>> b = 200
>>> a, b = b, a
>>> a
200
>>> b
100

使用 * 删除不需要的值。

>>> a, b, *_ = 1, 2, 0, 0, 0, 0
>>> a
1
>>> b
2
>>> _
[0, 0, 0, 0]

在上例中,不需要的信息存储在虚拟变量 _ 中,在后续的使用中可以忽略它。

默认情况下,Python 解释器使用下划线字符 _ 来存储在交互式会话中运行的语句的结果值。 因此,在这种情况下,使用这个字符来识别虚拟变量可能是模棱两可的。

在函数中返回元组。

>>> def powers(num):
...     return num, num ** 2, num ** 3
... 
>>> # Packaging returned values in a tuple
>>> result = powers(3)
>>> result
(3, 9, 27)
>>> # Unpacking returned values to multiple variables
>>> number, square, cube = powers(3)
>>> number
3
>>> square
9
>>> cube
27
>>> *_, cube = powers(3)
>>> cube
27

使用***运算符

使用*运算符合并迭代变量(iterables)。上面两个例子说明,这中方法也是连接迭代变量(iterables)的一种更易读和更有效的方法。

这个方法 (my_set) + my_list + list(my_tuple) + list(range(1, 4)) + list(my_str)可以生成一个列表 ,也可以使用更简洁的方法 [*my_set, *my_list, *my_tuple, *range(1, 4), *my_str]

>>> my_tuple = (1, 2, 3)
>>> (0, *my_tuple, 4)
(0, 1, 2, 3, 4)
>>> my_list = [1, 2, 3]
>>> [0, *my_list, 4]
[0, 1, 2, 3, 4]
>>> my_set = {1, 2, 3}
>>> {0, *my_set, 4}
{0, 1, 2, 3, 4}
>>> [*my_set, *my_list, *my_tuple, *range(1, 4)]
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> my_str = "123"
>>> [*my_set, *my_list, *my_tuple, *range(1, 4), *my_str]
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, '1', '2', '3']

使用 ** 运算符解包字典。

>>> numbers = {'one': 1, 'two': 2, 'three': 3}
>>> letters = {'a': 'A', 'b': 'B', 'c': 'C'}
>>> combination = {**numbers, **letters}
>>> combination
{'one': 1, 'two': 2, 'three': 3, 'a': 'A', 'b': 'B', 'c': 'C'}

需要注意的重要一点是,如果我们合并的字典具有重复键或公共键,则最右侧字典的值将覆盖最左侧字典的值。例如:

>>> letters = {'a': 'A', 'b': 'B', 'c': 'C'}
>>> vowels = {'a': 'a', 'e': 'e', 'i': 'i', 'o': 'o', 'u': 'u'}
>>> {**letters, **vowels}
{'a': 'a', 'b': 'B', 'c': 'C', 'e': 'e', 'i': 'i', 'o': 'o', 'u': 'u'}
>>> {**vowels, **letters}
{'a': 'A', 'e': 'e', 'i': 'i', 'o': 'o', 'u': 'u', 'b': 'B', 'c': 'C'}

通过 For-Loops 解包

我们还可以在 for 循环的上下文中使用可迭代解包。 当我们运行 for 循环时,在每次循环迭代中将其可迭代对象中的一项(item)分配给目标变量。 如果要分配的项(item)是可迭代的,那么我们可以使用元组作为目标变量,通过循环将可迭代对象解包到目标变量的元组中。

例如,我们可以构建一个包含两个元素的元组的列表。 每个元组将包含产品名称、价格和销售单位,我们通过 for 循环遍历每个元组元素来计算每个产品的收入。

>>> sales = [('Pencle', 0.22, 1500), ('Notebook', 1.30, 550), ('Eraser', 0.75, 1000)]
>>> for items in sales:
...     print(f"Income for {items[0]} is: {items[1] * items[2]}")
... 
Income for Pencle is: 330.0
Income for Notebook is: 715.0
Income for Eraser is: 750.0

我们可以使用索引来访问每个元组的各个元素。下面的示例代码中,在 for 循环使用解包,这也是 Python 中解包的一种实现。

>>> sales = [('Pencle', 0.22, 1500), ('Notebook', 1.30, 550), ('Eraser', 0.75, 1000)]
>>> for product, price, sold_units in sales:
...     print(f"Income for {product} is: {price * sold_units}")
... 
Income for Pencle is: 330.0
Income for Notebook is: 715.0
Income for Eraser is: 750.0

也可以在 for 循环中使用*运算符将多个项打包到单个目标变量中。 在下面这个例子中,我们首先取得每个序列的第一个元素。 其余值通过*运算符赋给目标变量 rest

>>> for first, *rest in [(1, 2, 3),(4, 5, 6)]:
...     print('First: ', first)
...     print('Rest: ', rest)
... 
First:  1
Rest:  [2, 3]
First:  4
Rest:  [5, 6]
>>> 

目标变量的结构必须与可迭代对象的结构一致,否则会报错。看下面的例子。

>>> data = [((1, 2), 3), ((2, 3), 3)]
>>> for (a, b), c in data:
...     print(a, b, c)
... 
1 2 3
2 3 3
>>> for a, b, c in data:
...     print(a, b, c)
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)

***定义函数

下面例子中的函数func至少需要一个名为required的参数。 它也可以接受一个或多个位置参数或关键字参数。 在这种情况下, * 运算符在一个叫 args 的元组中收集或打包额外的位置参数,而 ** 运算符在一个叫 kwargs 的字典中收集或打包额外的关键字参数。 argskwargs 都是可选的,并且分别自动默认为元组()和字典{}

这里 argskwargs 的命名并不是必须的,语法上只需要 *** 后跟有效标识符即可,建议给变量起个有意义的名字,提高代码的可读性。

>>> def func(required, *args, **kwargs):
...     print(required)
...     print(args)
...     print(kwargs)
... 
>>> func('Welcome to ...', 1, 2, 3, site='CloudAcademy.com')
Welcome to ...
(1, 2, 3)
{'site': 'CloudAcademy.com'}
>>> func('Welcome to ...', 1, 2, 3, 4)
Welcome to ...
(1, 2, 3, 4)
{}
>>> func('Welcome to ...', 1, 2, 3, (1, 2))
Welcome to ...
(1, 2, 3, (1, 2))
{}
>>> func('Welcome to ...', 1, 2, 3, [1, 2])
Welcome to ...
(1, 2, 3, [1, 2])
{}
>>> func('Welcome to ...', 1, 2, 3, ([2, 3], [1, 2]))
Welcome to ...
(1, 2, 3, ([2, 3], [1, 2]))
{}

使用***调用函数

调用函数时,我们还可以受益于使用 *** 运算符将参数集合分别解压缩为单独的位置参数或关键字参数。 这与在函数签名(signature of a function)中使用 *** 是相反的。 在函数签名中,运算符的意思是在一个标识符中收集或打包可变数量的参数。 在调用(calling)中,它们的意思是解包(unpack)一个可迭代对象到多个参数中。

续上例,* 运算符将像 ["Welcome", "to"] 这样的序列解包到位置参数中。 类似地, ** 运算符将字典解包为与字典的键值匹配的参数名。

>>> def func(welcome, to, site):
...     print(welcome, to, site)
... 
>>> func(*['Welcome', 'to'], **{'site': 'CloudAcademy.com'})
Welcome to CloudAcademy.com

综合运用前面的方法来编写非常灵活的函数,比如,在定义和调用 Python 函数时,更灵活的使用 *** 运算符。 例如:

>>> def func(required, *args, **kwargs):
...     print(required)
...     print(args)
...     print(kwargs)
... 
>>> func('Welcome to...', *(1, 2, 3), **{'Site': 'CloudAcademy.com'})
Welcome to...
(1, 2, 3)
{'Site': 'CloudAcademy.com'}

总结

可迭代解包(iterable unpacking)这个特性允许我们将一个可迭代对象解包成几个变量。 另一方面,打包包括使用解包运算符 * 将多个值赋到一个变量中。

可迭代解包(iterable unpacking)也可以用来进行并行赋值和变量之间的值交换,也可以用在 for 循环、函数调用和函数定义中。