Python 3.5 的解包操作符
引言
今天我想要聊聊 Python 中用于解包(Unpacking)的两个操作符号——*
和 **
基本用法
*
最为常见的用法是用来表示乘法。但我们也可以将 *
用于任意一个可迭代对象(iterable object)1上,表示我们想要提取里面所有的值
📒 Python 自带的可迭代对象包括:
list
,tuple
,set
, 和dict
Starred assignment/expression
随着 Python 3.0 的发布,Python 支持使用 *
来解包任意可迭代对象2。这个被称之为 Starred assignment,也叫做 Starred expressions。我也有看到称之为 parallel assignment 的。
我们可以在 =
左边声明一个特殊的变量表示捕获所有变量。看下面这个直观的例子:
>>> first, *rest, last = [1, 2, 3, 4, 5]
>>> first
1
>>> rest
[2, 3, 4]
>>> last
5
📒 语法很简单:一个星号后面紧跟着变量名 -
*foo
。我们可以在=
左边任意位置放置它,但是只能最多使用一次这样的变量。以及,foo
的类型为list
在我看来,Python 的这个特性很大程度上提高了代码的可读性
不过它也有一些局限:
- 我们不能仅仅在
=
左边使用一个*foo
当成唯一的被赋值目标。=
左侧必须是一个 list 或者是 tuple - 如果
=
右边的值的数量不够用于解包的话,程序就会报错
下面的例子证明了这两点:
*first = [1, 2, 3]
Cell In [1], line 1
*first = [1, 2, 3]
^
SyntaxError: starred assignment target must be in a list or tuple
# just add `,` would be fine
# now the LHS is a tuple
*first, = [1, 2, 3]
first
[1, 2, 3]
first, second, *rest = [1]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In [3], line 1
----> 1 first, second, *rest = [1]
ValueError: not enough values to unpack (expected at least 2, got 1)
📒 常见的一个做法是将
*
和_
结合在一起使用(也就是*_
),表示我们不关心它捕获的变量
first, *_ = [1, 2, 3]
first
更进一步
从 Python 3.5 开始,我们可以在更多的情况下使用 *
和 **
3
情况1⃣️:在函数调用里面,我们想要使用几次就可以使用几次
foo, bar = {'a': 1, 'b': 2}, {'c': 3, 'd': 4}
dict(**foo, **bar) # dict is a function
📒
dict
中的 keys 的优先级是右边大于左边。换句话说,后面出现的 key 的值总是会覆盖前面出现的。看下面这个例子
{**{'a': 1, 'b': 2}, **{'a': 3}}
{'a': 3, 'b': 2}
📒当我们在函数调用里面使用
**
的时候,需要注意一个问题:我们需要确保没有重复的 key
dict(**{'a': 1, 'b': 2}, **{'a': 3})
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [5], line 1
----> 1 dict(**{'a': 1, 'b': 2}, **{'a': 3})
TypeError: dict() got multiple values for keyword argument 'a'
情况2⃣️:我们可以在 tuple/list/set/dict 字面值中使用。但是不能在 list/set/dict comprehensions 里面使用😺
# an example drawn from PEP 448
>>> *range(4), 4
(0, 1, 2, 3, 4)
>>> [*range(4), 4]
[0, 1, 2, 3, 4]
>>> {*range(4), 4}
{0, 1, 2, 3, 4}
>>> {'x': 1, **{'y': 2}}
{'x': 1, 'y': 2}
{'x': 1, 'y': 2}
matrix = [
[1, 2, 3]
[4, 5, 6]
]
[*sublist for sublist in matrix]
Cell In [7], line 5
[*sublist for sublist in matrix]
^
SyntaxError: iterable unpacking cannot be used in comprehension
总结
Python 的解包操作符让编程轻松很多。它提供了一种直观的解构可迭代对象的手段。在这个操作符的帮助下,我们可以避免一些愚蠢的索引错误🙅