打包和解包 ¶
解包Unpacking ¶
Python 允许变量的元组(或列表)出现在赋值操作的左侧。
元组中的每个变量都可以从赋值右侧的可迭代对象(iterable)中接收一个值(或者更多,如果我们使用 * 运算符)。 Python 中的解包是指一种操作,该操作包括在单个赋值语句中将可迭代的值分配给变量的元组(或列表)。
在 Python 中,可以在赋值运算符=
的左侧放置一个变量元组,在右侧放置一个值元组。 右边的值将根据它们在元组中的位置自动分配给左边的变量。这在 Python 中通常称为 元组解包。
如下示例:
(a, b, c) = (1, 2, 3)
print(a, b, c) # 1 2 3
birthday = ("April", 5, 2001)
month, day, year = birthday
print(month) # April
print(day) # 5
print(year) # 2001
# 注意赋值的顺序调整后,输出结果也会随之改变
day, month, year = birthday
print(month) # 5
print(day) # April
print(year) # 2001
元组解包功能在 Python 中可以扩展为适用于任何可迭代对象。 唯一的要求是可迭代的接收元组(或列表)中的每个变量恰好对应可迭代对象的一个元素(item)。
下面的示例介绍了 Python 中可迭代解包的工作原理:
# 解包字符串
a, b, c = '123'
print(a, b, c) # 1 2 3
# 解包列表
a, b, c = [1, 2, 3]
print(a, b, c) # 1 2 3
# 解包生成器
gen = (i ** 2 for i in range(3))
a, b, c = gen
print(a, b, c) # 0 1 4
# 解包字典(键、值和项)
my_dict = {'one': 1, 'two': 2, 'three': 3}
a, b, c = my_dict
print(a, b, c) # one two three
a, b, c = my_dict.values()
print(a, b, c) # 1 2 3
a, b, c = my_dict.items()
print(a, b, c) # ('one', 1) ('two', 2) ('three', 3)
# 在赋值语句的右侧使用元组
[a, b, c] = 1, 2, 3
print(a, b, c) # 1 2 3
# 使用 range() 迭代器赋值
x, y, z = range(3)
print(x, y, z) # 0 1 2
打包Packing ¶
打包可以理解为使用可迭代解包运算符在单个变量中收集多个值。在这种情况下, *
运算符被称为元组(或可迭代)解包运算符。 它扩展了解包功能,允许在单个变量中收集或打包多个值。
在以下示例中可以看到 *
运算符将元组值打包到单个变量中:
(*a,) = 1, 2
print(a) # [1, 2]
print(type(a)) # <class 'list'>
在上面的代码中,赋值的左侧必须是元组(或列表),这就是使用尾随逗号的原因。这个元组可以包含所需要的尽可能多的变量,但是,它只能包含一个星号表达式(starred expression)。
# 打包尾随值
a, *b = 1, 2, 3
print(a, b) # 1 [2, 3]
print(type(a)) # <class 'int'>
print(type(b)) # <class 'list'>
*a, b, c = 1, 2, 3
print(a, b, c) # [1] 2 3
*a, b, c, d, e = 1, 2, 3
print(a, b, c, d, e) # ValueError: not enough values to unpack (expected at least 4, got 3)
*a, b, c, d = 1, 2, 3
print(a, b, c, d) # [] 1 2 3
seq = [1, 2, 3, 4]
first, *body, last = seq
print(first, body, last) # 1 [2, 3] 4
first, body, *last = seq
print(first, body, last) # 1 2 [3, 4]
ran = range(10)
*r, = ran
print(r) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
下面是一些打包和解包的例子。
employee = ["John", "40", "Software Engineer"]
name = employee[0]
age = employee[1]
job = employee[2]
print(name, age, job) # John 40 Software Engineer
name, age, job = ["John", "40", "Software Engineer"]
print(name, age, job) # John 40 Software Engineer
a = 100
b = 200
a, b = b, a
print(a, b) # 200 100
使用 *
删除不需要的值。
a, b, *_ = 1, 2, 0, 0, 0, 0
print(a, b, _) # 1 2 [0, 0, 0, 0]
在上例中,不需要的信息存储在虚拟变量 _
中,在后续的使用中可以忽略它。
默认情况下,Python 解释器使用下划线字符 _
来存储在交互式会话中运行的语句的结果值。 因此,在这种情况下,使用这个字符来识别虚拟变量可能是模棱两可的。
在函数中返回元组。
def powers(num):
return num, num**2, num**3
# 打包返回值到一个元组中
result = powers(3)
print(result) # (3, 9, 27)
# 解包返回值到多个变量中
number, square, cube = powers(3)
print(number, square, cube) # 3 9 27
*_, cube = powers(3)
print(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)
print((0, *my_tuple, 4)) # (0, 1, 2, 3, 4)
my_list = [1, 2, 3]
print([0, *my_list, 4]) # [0, 1, 2, 3, 4]
my_set = {1, 2, 3}
print({0, *my_set, 4}) # {0, 1, 2, 3, 4}
print([*my_set, *my_list, *my_tuple, *range(1, 4)]) # [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
my_str = "123"
print([*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}
print(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"}
print({**letters, **vowels})
# {'a': 'a', 'b': 'B', 'c': 'C', 'e': 'e', 'i': 'i', 'o': 'o', 'u': 'u'}
print({**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)
# ValueError: not enough values to unpack (expected 3, got 2)
用*
和**
定义函数 ¶
下面例子中的函数func至少需要一个名为required
的参数。 它也可以接受一个或多个位置参数或关键字参数。 在这种情况下, *
运算符在一个叫 args
的元组中收集或打包额外的位置参数,而 **
运算符在一个叫 kwargs
的字典中收集或打包额外的关键字参数。 args
和 kwargs
都是可选的,并且分别自动默认为元组()
和字典{}
。
这里 args
和 kwargs
的命名并不是必须的,语法上只需要 *
或 **
后跟有效标识符即可,建议给变量起个有意义的名字,提高代码的可读性。
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 循环、函数调用和函数定义中。