跳转到内容

解包

解包的价值不在于省字符,在于不用写下标。下标是硬编码的位置,写错一个数字, 代码照样能跑,只是结果悄悄错了。解包把「第 0 个是什么、第 1 个是什么」写成了名字。

而且解包不只对元组有效,对任何可迭代对象都有效 —— 包括不支持 [] 的迭代器。

>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates

交换两个变量不用临时变量:

>>> b, a = a, b

其实右边先造了一个元组 (a, b),再解包回去。所以这不是什么语法糖特例,就是解包。

函数调用时加 * 把序列摊开:

>>> t = (20, 8)
>>> divmod(*t)
(2, 4)
>>> quotient, remainder = divmod(*t)

函数返回多个值就是靠这个:返回元组,调用方解包。

>>> import os
>>> _, filename = os.path.split('/home/luciano/.ssh/id_rsa.pub')
>>> filename
'id_rsa.pub'

os.path.split() 返回 (head, tail) 两个值。只要文件名,就用 _ 丢掉头。

*args 收任意多个位置参数是老特性。Python 3 把它扩展到了赋值语句:

>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
>>> a, b, *rest = range(2) # 一个都不剩也行
>>> a, b, rest
(0, 1, [])

三个必须记住的规则:

  1. 一个赋值里只能有一个 *。
  2. * 可以在任何位置,不用非得在末尾。
  3. rest 永远是 list,哪怕只有一个元素,哪怕是空的。
>>> a, *body, c, d = range(5)
>>> a, body, c, d
(0, [1, 2], 3, 4)
>>> *head, b, c, d = range(5)
>>> head, b, c, d
([0, 1], 2, 3, 4)

* 还能用在函数调用和字面量里(PEP 448)

Section titled “* 还能用在函数调用和字面量里(PEP 448)”

函数调用里可以出现多个 *:

>>> def fun(a, b, c, d, *rest):
... return a, b, c, d, rest
>>> fun(*[1, 2], 3, *range(4, 7))
(1, 2, 3, 4, (5, 6))

构造 list / tuple / set 字面量时也能用:

>>> *range(4), 4
(0, 1, 2, 3, 4)
>>> [*range(4), 4]
[0, 1, 2, 3, 4]
>>> {*range(4), 4, *(5, 6, 7)}
{0, 1, 2, 3, 4, 5, 6, 7}

解包的目标可以带嵌套,只要值的结构对得上:

for name, _, _, (lat, lon) in metro_areas:
if lon <= 0:
print(f'{name:15} | {lat:9.4f} | {lon:9.4f}')

最后那个字段 (35.689722, 139.691667) 直接在 for 里被拆成 lat 和 lon, 一行都不用多写。

大多数时候解包到元组就够了。主要有用的场景是「确认查询结果只有一条」:

>>> [record] = query_returning_single_row()

这不是解包语法,是列表解包:如果查询返回了两条,会抛 ValueError。 比先取出来再检查长度更省事。

只有一个字段的话:

>>> [[field]] = query_returning_single_row_with_single_field()

tests/test_ch02.py 里 test_nested_unpacking_and_match_agree 断言了嵌套解包 和 match/case 两种写法得到同样的结果。