接續昨天 unpacking 的學習,今天我們來練習更進階的 unpacking。
假設我們現在分別要得到一個陣列的首位元素和剩餘元素,可能會這樣寫:
l = [1, 2, 3, 4, 5, 6]
a = l[0]
b = l[1:]
print(a)
print(b)
1
[2, 3, 4, 5, 6]
我們可以用 unpacking 技巧稍微簡化程式碼:
a, b = l[0], l[1:]
print(a)
print(b)
1
[2, 3, 4, 5, 6]
然而,Python3.6 以後引進了一個更酷炫的語法: * 運算元,它可以做到同樣的事:
a, *b = l
print(a)
print(b)
1
[2, 3, 4, 5, 6]
注意等號左邊的 * 運算元只能出現一次!
就像標準的 unpacking 一樣,這種延伸的 unpacking 方式也適用所有可迭代物件:
With tuples:
a, *b = -10, 5, 2, 100
print(a)
print(b)
-10
[5, 2, 100]
With strings:
a, *b = 'python'
print(a)
print(b)
p
['y', 't', 'h', 'o', 'n']
現在假設,我們需要取得首個、第二個、最後一個和其他所有元素。
我們一樣可以用 slicing 做到:
s = 'python'
a, b, c, d = s[0], s[1], s[2:-1], s[-1]
print(a)
print(b)
print(c)
print(d)
p
y
tho
n
但使用 unpacking 技巧就讓一切變得更輕鬆了:
a, b, *c, d = s
print(a)
print(b)
print(c)
print(d)
p
y
['t', 'h', 'o']
n
如你所見, c 是 一群字母組成的陣列,並非字串。
如果你堅持要字串,就可以簡單地再加工:
print(c)
c = ''.join(c)
print(c)
['t', 'h', 'o']
tho
更酷的可能是,我們可以在等號右邊 unpacking,而且沒有等號左邊的『"*"只能出現一次』的限制:
l1 = [1, 2, 3]
l2 = [4, 5, 6]
l = [*l1, *l2]
print(l)
[1, 2, 3, 4, 5, 6]
l1 = [1, 2, 3]
s = 'ABC'
l = [*l1, *s]
print(l)
[1, 2, 3, 'A', 'B', 'C']
也可以對 set 或 dictionary 做這種 unpacking。
只是對 set 來說,這種 unpacking 可能不太好用,因為 set 是無序的,你恐怕不知道自己 unpacking 拿到什麼:
s = {10, -99, 3, 'd'}
for c in s:
print(c)
10
3
-99
d
如你所見,unpacking 得到的元素順序和 set 創建時已經不同了。
s = {10, -99, 3, 'd'}
a, b, *c = s
print(a)
print(b)
print(c)
10
3
[-99, 'd']
那對 set unpacking 就都沒用嗎?
錯!看一下下面的 unpacking:
s = {10, -99, 3, 'd'}
*a, = s
print(a)
[10, 3, -99, 'd']
乍看之下沒什麼用,我們只是把 set 中的元素解開放入一個陣列,但這其實對 set 的運算很有幫助!
s1 = {1, 2, 3}
s2 = {3, 4, 5}
我們要怎麼把上面兩個 set 融合在一起?
s1 + s2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/Users/maotingyang/Downloads/Extended Unpacking.ipynb Cell 35 in <cell line: 1>()
----> <a href='vscode-notebook-cell:/Users/maotingyang/Downloads/Extended%20Unpacking.ipynb#X46sZmlsZQ%3D%3D?line=0'>1</a> s1 + s2
TypeError: unsupported operand type(s) for +: 'set' and 'set'
看來加法沒用⋯⋯
我們可以用 set 內建的 union 方法:
print(s1)
print(s2)
s1.union(s2)
{1, 2, 3}
{3, 4, 5}
{1, 2, 3, 4, 5}
水!那如果我們要融合四個 set 呢?
s1 = {1, 2, 3}
s2 = {3, 4, 5}
s3 = {5, 6, 7}
s4 = {7, 8, 9}
print(s1.union(s2).union(s3).union(s4))
print(s1.union(s2, s3, s4))
{1, 2, 3, 4, 5, 6, 7, 8, 9}
{1, 2, 3, 4, 5, 6, 7, 8, 9}
寫起來比較累了,也許⋯⋯利用 unpacking?
{*s1, *s2, *s3, *s4}
{1, 2, 3, 4, 5, 6, 7, 8, 9}
我們直接把一個 set unpacking 到另一個 set,太神了!
這招對 dictionaries 一樣適用,只要記得 * 只是 unpacking dictionary 的 key:
d1 = {'key1': 1, 'key2': 2}
d2 = {'key2': 3, 'key3': 3}
[*d1, *d2]
['key1', 'key2', 'key2', 'key3']
有沒有辦法同時 unpack key-value 呢?
有的!我們可以用 ** operator:
d1 = {'key1': 1, 'key2': 2}
d2 = {'key2': 3, 'key3': 3}
{**d1, **d2}
{'key1': 1, 'key2': 3, 'key3': 3}
要注意,key2 的值變成了 3,因為 d2 在 d1 之後 unpacking,所以 d1 的值被 d2 覆寫:
{**d2, **d1}
{'key1': 1, 'key2': 2, 'key3': 3}
如此一來,字典也可以在字典中 unpacking,把字典融合了:
{'a': 1, 'b': 2, **d1, **d2, 'c':3}
{'a': 1, 'b': 2, 'key1': 1, 'key2': 3, 'key3': 3, 'c': 3}
Python 甚至可以巢狀 unpacking:
a, b, (c, d) = [1, 2, ['X', 'Y']]
print(a)
print(b)
print(c)
print(d)
1
2
X
Y
a, b, (c, d) = [1, 2, 'XY']
print(a)
print(b)
print(c)
print(d)
1
2
X
Y
We can even write something like this:
a, b, (c, d, *e) = [1, 2, 'python']
print(a)
print(b)
print(c)
print(d)
print(e)
1
2
p
y
['t', 'h', 'o', 'n']
記得上面說到,等號左邊只能有一個 * unpacking⋯⋯
巢狀不在此限:
a, *b, (c, d, *e) = [1, 2, 3, 'python']
print(a)
print(b)
print(c)
print(d)
print(e)
1
[2, 3]
p
y
['t', 'h', 'o', 'n']
大部分解如下
a, *b, tmp = [1, 2, 3, 'python']
print(a)
print(b)
print(tmp)
1
[2, 3]
python
c, d, *e = tmp
print(c)
print(d)
print(e)
p
y
['t', 'h', 'o', 'n']
如果我們要用 slicing 達到跟上面同樣的效果:
l = [1, 2, 3, 'python']
l[0], l[1:-1], l[-1][0], l[-1][1], list(l[-1][2:])
(1, [2, 3], 'p', 'y', ['t', 'h', 'o', 'n'])
l = [1, 2, 3, 'python']
a, b, c, d, e = l[0], l[1:-1], l[-1][0], l[-1][1], list(l[-1][2:])
print(a)
print(b)
print(c)
print(d)
print(e)
1
[2, 3]
p
y
['t', 'h', 'o', 'n']
可以看到,相較這麼複雜的 slicing,用*
unpacking 優雅許多。
好啦,我們明天見!
參考:Python 3: Deep Dive (Part 1 - Functional)