解包指的是使用疊代,從集合(例如 list、tuple 或 dict)中取出元素的動作。
解包出來的值接著可以在同一道敘述中指定給變數,這通常稱為多重賦值。
特殊運算子 * 和 ** 經常出現在解包的場合,也常與多重賦值一起使用。
*<variable_name> 和 **<variable_name> 不該與 * 和 ** 搞混。* 和 ** 分別用於乘法和指數運算,而 *<variable_name> 和 **<variable_name> 則是打包與解包運算子。
在多重賦值中,賦值運算子(=)左邊的變數數量,必須與右邊的值的數量相符。
若要分隔這些值,請使用逗號 ,:
>>> a, b = 1, 2
>>> a
1
如果多重賦值拿到的變數數量與給定的值不符,就會擲出 ValueError:
>>> x, y, z = 1, 2
ValueError: too many values to unpack (expected 3, got 2)
多重賦值並不限於單一資料型態:
>>> x, y, z = 1, "Hello", True
>>> x
1
>>> y
'Hello'
>>> z
True
多重賦值可以用來交換 lists 中的元素。
這種做法在排序演算法中相當常見。
例如:
>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]
由於 tuples 是不可變的,你無法在 tuple 中交換元素。
底下的範例使用 lists,但同樣的概念也適用於 tuples。
在 Python 中,可以把 list/tuple/dictionary 的元素解包到各個不同的變數中。
由於 lists/tuples 中的值有固定的順序,它們會依照同樣的順序解包到變數中:
>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"
如果有不需要的值,可以用 _ 標記它們:
>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"
從包在 list 或 tuple 裡的 list/tuple(也就是巢狀陣列/元組)中解包並指定值,做法和淺層解包相同,但通常需要加上限定詞來說明情境或位置:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"
>>> d
"potato"
你也可以只深層解包巢狀 list/tuple 的其中一部分:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]
>>> c
"carrot"
如果解包時變數的位置不正確,或值的數量不正確,就會得到 ValueError:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables
ValueError: too many values to unpack (expected 1)
* 解包陣列/元組在解包 list/tuple時,你可以用 * 運算子捕捉「剩下的」值。
這比對 list/tuple 切片(在某些情況下可讀性較差)更清楚。
例如,可以取出第一個元素,再把剩下的值放進一個不含第一個元素的新 list:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"
>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]
我們也可以取出 list 開頭和結尾的值,同時把中間所有的值分成一組:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"
>>> middle
["banana", "cherry", "orange", "kiwi"]
我們也可以在深層解包時使用 *:
>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"
>>> rest
["banana", "melon"]
解包字典與解包 list/tuple 有點不同。
對字典進行疊代時,預設會疊代鍵。
所以解包 dict 時,只能解包鍵,不能解包值:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"
如果想解包值,可以使用 <dict>.values() 方法:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6
如果鍵和值都需要,就使用 <dict>.items() 方法。
<dict>.items() 會產生一個包含鍵值配對的 iterable view。
這些配對可以解包到 tuple 中:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)
打包是指把多個值組成一個 list 並指定給變數的能力。
當你想要_解包_值、做些修改,再把結果_打包_回一個變數時,這就很有用。
它也能對 2 個以上的 lists/tuples/dicts 進行合併。
* 打包陣列/元組打包 list/tuple 可以用 * 運算子來完成。
這會把所有值打包成一個 list/tuple。
>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits
# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")
# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma.
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
關於 * 用在等號左邊的更多背景,請參閱 PEP 3132。
** 打包字典打包字典是使用 ** 運算子來完成的。
這會把一個字典中所有的鍵-值配對打包進另一個字典,或把兩個字典合併在一起。
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
* 和 ** 在函式中的用法當你建立的函式要接受任意數量的引數時,可以在函式定義中使用 *args 或 **kwargs。
*args 用來把任意數量的位置(非關鍵字)引數打包成 tuple,
**kwargs 則用來把任意數量的關鍵字引數打包成字典。
*args 的用法:
# This function is defined to take any number of positional arguments
>>> def my_function(*args):
... print(args)
# Arguments given to the function are packed into a tuple
>>> my_function(1, 2, 3)
(1, 2, 3)
>>> my_function("Hello")
("Hello")
>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")
**kwargs 的用法:
# This function is defined to take any number of keyword arguments
>>> def my_function(**kwargs):
... print(kwargs)
# Arguments given to the function are packed into a dictionary
>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}
*args 和 **kwargs 也可以彼此搭配使用:
>>> def my_function(*args, **kwargs):
... print(sum(args))
... for key, value in kwargs.items():
... print(str(key) + " = " + str(value))
>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3
你也可以把參數寫在 *args 前面,以指定特定的位置引數。
這時個別的關鍵字引數就必須放在 **kwargs 前面。
def my_function(<positional_args>, *args, <key-word_args>, **kwargs)
如果不遵守這個順序,就會出現錯誤。
>>> def my_function(a, b, *args):
... print(a)
... print(b)
... print(args)
>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
把引數寫成錯誤的順序會導致錯誤:
>>>def my_function(*args, a, b):
... print(args)
>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "c:\something.py", line 3, in <module>
my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'
你可以用 * 把 list/tuple 的引數解包到函式呼叫中。
對於不接受 iterable 的函式來說,這非常實用:
>>> def my_function(a, b, c):
... print(c)
... print(b)
... print(a)
numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1
把 * 解包搭配 zip() 內建函式 使用,是另一個常見的用法。
zip() 函式接受多個 iterable,並回傳一個 list,裡面是將每個 iterable 的值分組而成的 tuples:
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]
你的朋友 Linus 是位火車頭駕駛員,開著貨運列車往返於城市之間。 雖然他們很擅長處理列車的事,卻不太擅長處理物流或電腦。 他們想請你運用程式設計的專長,幫忙整理列車的細節,並修正路線資料中的錯誤。
這道練習用切片、索引和各種 dict 方法就能輕鬆解決。
不過,我們希望你在解決以下每個任務的過程中,練習打包、解包和多重指派。
你的朋友一直在記錄每個車廂的識別碼(ID),但他們永遠不確定系統在任何時刻需要處理多少個車廂。如果把這些資料打包成單一的 list,對物流程式的其餘部分來說會方便得多。
實作一個 get_list_of_wagons() 函式,接受任意數量的車廂 ID。
每個 ID 都會是正整數。
這個函式接著應該把傳入的 ID 以單一 list 的形式 return。
>>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
[1, 7, 12, 3, 14, 8, 5]
到了這裡,你已經開始對這些資料,以及它在物流程式中的用法有些感覺了。 ID 系統一律把火車頭的 ID 指派為 1,列車中其餘的車廂則隨機指派一個大於 1 的 ID。
你的朋友剛把兩節新車廂連上列車,卻忘了更新系統!
現在,列車 list 中的前兩個車廂必須移到最後,否則一切順序都會亂掉。
更麻煩的是,你的朋友剛剛又發現了第二個 list,裡面似乎包含了遺漏的車廂 ID。
他們只記得,新的車廂移好之後,第二個 list 裡的 ID 應該直接接在指定的火車頭後面。
Linus 會非常感謝你幫他們修正錯誤並整合資料。
實作一個 fix_list_of_wagons() 函式,接受兩個包含車廂 ID 的 lists。
它應該把第一個 list 的前兩個項目移到最後,並把第二個 list 中的值插入火車頭 ID(1)後方(也就是它的右側)。
這個函式接著應該 return 一個經過修改的 list。
>>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
現在所有車廂資料都正確了,Linus 希望你更新系統的路線資訊。
沿著運輸路線,列車可能會在幾個不同的車站停靠,以裝卸貨物。
每趟旅程的中途交貨點數量都可能不同。
你的朋友希望你用任何缺少或額外的交貨資訊,更新系統的路線 dict。
實作一個 add_missing_stops() 函式,接受一個路線 dict,後面接著數量不固定的關鍵字引數。
這些引數的形式可以是包含一個或多個停靠站的 dict,也可以是任意數量的 stop_number=city 關鍵字配對。
你的函式接著應該回傳更新後的路線 dict,其中多了一個 key,存放依序加入的所有停靠站 list。
>>> add_missing_stops({"from": "New York", "to": "Miami"},
stop_1="Washington, DC", stop_2="Charlotte", stop_3="Atlanta",
stop_4="Jacksonville", stop_5="Orlando")
{"from": "New York", "to": "Miami", "stops": ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
Linus 一直在開發路線程式,並注意到某些路線缺少了一些重要細節。
最初的路線資訊是以 dict 建構的,你的朋友希望你用任何可能缺少的內容更新那個 dict。
系統中的每條路線都需要稍微不同的細節,所以 Linus 真的很希望有個通用的解決方案。
實作一個名為 extend_route_information() 的函式,接受兩個 dicts。
第一個 dict 包含列車路線往返的起點與終點城市。
第二個 dict 包含其他路線細節,例如列車速度、長度或溫度。
這個函式應該回傳一個整合了所有路線資訊的 dict。
第二個 dict 可以包含與範例所示不同或更多的屬性。
>>> extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
{"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Linus 在勘查車廂停放場時,注意到車廂沒有按照正確的順序存放。 除了 ID 之外,每個車廂還有對應其載運貨物種類的顏色。 車廂在停放場中以網格存放,網格裡的每一欄都是相同顏色的車廂。
然而,物流系統顯示要存放進停放場的車廂 lists,其_列_卻按顏色分組。
但要讓存放網格正常運作,每一_列_應該有三種不同的顏色,這樣_欄_才能按顏色對齊。
你的朋友希望你整理停放場的車廂 lists,讓車廂能正確存放。
實作一個名為 fix_wagon_depot() 的函式,接受一個包含三個項目的 list。
每個 list 項目都是一個子陣列(或「列」),內含三個 tuples。
每個 tuple 都是一組 (<wagon ID>, <wagon color>) 配對。
你的函式應該回傳一個 list,其中三個「列」lists 已重新排序,讓車廂交換到正確的位置。
>>> fix_wagon_depot([
[(2, "red"), (4, "red"), (8, "red")],
[(5, "blue"), (9, "blue"), (13,"blue")],
[(3, "orange"), (7, "orange"), (11, "orange")],
])
[
[(2, "red"), (5, "blue"), (3, "orange")],
[(4, "red"), (9, "blue"), (7, "orange")],
[(8, "red"), (13,"blue"), (11, "orange")]
]