關於Python 解包,你需要知道的一切
導讀:本文總結了 Python 解包操作的方方面面,文章略長,看本文前,首先確保身邊有多個不同版本 Python 直譯器的電腦(公眾號回覆 conda ,瞭解如何安裝多個環境),以便隨時驗證程式碼。看完記得收藏,方便查閱)
解包在英文裡叫做 Unpacking,就是將容器裡面的元素逐個取出來(防槓精:此處描述並不嚴謹,因為容器中的元素並沒有發生改變)放在其它地方,好比你老婆去菜市場買了一袋蘋果回來分別發給家裡的每個成員,這個過程就是解包。Python 中的解包是自動完成的,例如:
>>> a, b, c = [1,2,3] >>> a 1>>> b 2 >>> c 3
如果列表中有3個元素,那麼剛好如果列表中有3個元素,那麼剛好可以分配給3個變數。除了列表物件可以解包之外,任何可迭代物件都支援解包,可迭代物件包括元組、字典、集合、字串、生成器等實現了__next__方法的一切物件。
元組解包
>>> a,b,c = (1,2,3) >>> a 1 >>> b 2 >>> c 3
字串解包
>>> a,b,c = "abc" >>> a 'a' >>> b'b' >>> c 'c'
字典解包
>>> a,b,c = {"a":1, "b":2, "c":3} >>> a 'a' >>> b 'b' >>> c 'c'
字典解包後,只會把字典的 key 取出來,value 則丟掉了。
你可能見過多變數賦值操作,例如:
>>> a, b = 1, 2 >>> a 1 >>> b 2
本質上也是自動解包過程,等號右邊其實是一個元組物件 (1, 2)
,有時候我們程式碼不小心多了一個逗號 ,
,就變成了元組物件
>>> a = 1, >>> a (1,) ---------- >>> a = 1 >>> a 1
所以寫程式碼的時候需要特別注意。在Python 中,交換兩個變數非常方便,本質上也是自動解包過程。
>>> a, b = 1, 2 >>> a, b = b, a >>> a 2 >>> b 1
如果在解包過程中,遇到左邊變數個數小於右邊可迭代物件中元素的個數時該怎麼辦? 好比你們家有3口人,你老婆卻買了4個蘋果,怎麼分配呢?
在Python2中,如果等號左邊變數的個數不等於右邊可迭代物件中元素的個數,是不允許解包的。但在 Python3 可以這麼做了。這個特性可以在 PEP 3132 中看到。
>>> a, b, *c = [1,2,3,4] >>> a 1 >>> b 2 >>> c [3, 4] >>>
這種語法就是在某個變數面前加一個星號,而且這個星號可以放在任意變數,每個變數都分配一個元素後,剩下的元素都分配給這個帶星號的變數
>>> a, *b, c = [1,2,3,4] >>> a 1 >>> b [2, 3] >>> c 4
這種語法有什麼好處呢?它使得你的程式碼寫起來更簡潔,比如上面例子,在 Python2 中該怎麼操作呢?思考3秒鐘,再看答案。
>>> n = [1,2,3,4] # 使用切片操作 >>> a, b, c = n[0], n[1:-1], n[-1] >>> a 1 >>> b [2, 3] >>> c 4
以上是表示式解包的一些操作,接下來介紹函式呼叫時的解包操作。函式呼叫時,有時你可能會用到兩個符號:星號*
和 雙星號**
。
>>> def func(a,b,c): ... print(a,b,c) ... >>> func(1,2,3) 1 2 3
func 函式定義了三個位置引數 a,b,c,呼叫該函式必須傳入三個引數,除此之外,你也可以傳入包含有3個元素的可迭代物件,
>>> func(*[1,2,3]) 1 2 3 >>> func(*(1,2,3)) 1 2 3 >>> func(*"abc") a b c >>> func(*{"a":1,"b":2,"c":3}) a b c
函式被呼叫的時候,使用星號 *
解包一個可迭代物件作為函式的引數。字典物件,可以使用兩個星號,解包之後將作為關鍵字引數傳遞給函式
>>> func(**{"a":1,"b":2,"c":3}) 1 2 3
看到了嗎?和上面例子的區別是多了一個星號,結果完全不一樣,原因是什麼? 答案是 **
符號作用的物件是字典物件,它會自動解包成關鍵字引數 key=value 的格式:
>>> func(a=1,b=2,c=3) 1 2 3
如果字典物件中的 key 不是 a,b,c,會出現什麼情況?請讀者自行測試。
總結一下,函式呼叫時,一個星號可作用於所有的可迭代物件,稱為迭代器解包操作,作為位置引數傳遞給函式,兩個星號只能作用於字典物件,稱之為字典解包操作,作為關鍵字引數傳遞給函式。使用 *
和 **
的解包的好處是能節省程式碼量,使得程式碼看起來更優雅,不然你得這樣寫:
>>> d = {"a":1, "b":2, "c":3} >>> func(a = d['a'], b=d['b'], c=d['c']) 1 2 3 >>>
到這裡,解包還沒介紹完,因為 Python3.5,也就是 PEP 448 對解包操作做了進一步的擴充套件, 在 3.5 之前的版本,函式呼叫時,一個函式中解包操作只允許一個*
和 一個**
。從 3.5 開始,在函式呼叫中,可以有任意多個解包操作,例如:
# Python 3.4 中 print 函式 不允許多個 * 操作 >>> print(*[1,2,3], *[3,4]) File "<stdin>", line 1 print(*[1,2,3], *[3,4]) ^ SyntaxError: invalid syntax >>>
再來看看 python3.5以上版本
# 可以使用任意多個解包操作 >>> print(*[1], *[2], 3) 1 2 3
從 3.5 開始可以接受多個解包,於此同時,解包操作除了用在函式呼叫,還可以作用在表示式中。
>>> *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}
新的語法使得我們的程式碼更加優雅了,例如拼接兩個列表可以這樣:
>>> list1 = [1,2,3] >>> list2 = range(3,6) >>> [*list1, *list3] [1, 2, 3, 3, 4, 5] >>>
可不可以直接用 +
操作呢?不行,因為 list 型別無法與 range 物件 相加,你必須先將 list2 強制轉換為 list 物件才能做 +
操作,這個留給讀者自行驗證。
再來看一個例子:如何優雅的合併兩個字典
>>> a = {"a":1, "b":2} >>> b = {"c":3, "d":4} >>> {**a, **b} {'a': 1, 'b': 2, 'c': 3, 'd': 4}
在3.5之前的版本,你不得不寫更多的程式碼:
>>> import copy >>> >>> c = copy.deepcopy(a) >>> c.update(b) >>> c {'a': 1, 'b': 2, 'c': 3, 'd': 4}
最後給你總結一下:
- 自動解包支援一切可迭代物件
- python3中,開始支援更高階的解包操作,用星號操作使得等號左邊的變數個數可以少於右邊迭代物件中元素的個數。
- 函式呼叫時,可以用 * 或者 ** 解包可迭代物件
- python3.5,函式呼叫和表示式中可支援更多的解包引數。
關注公眾號「Python之禪」(id:vttalk)獲取最新文章