軌道
/
Python
Python
/
課程大綱
/
解包與多重賦值
解包

解包與多重賦值 在 Python

3 個練習

關於 解包與多重賦值

解包是指透過疊代,從集合(例如list、tuple或dict)中取出元素的動作。 解包後的值接著可以在同一道敘述中賦值給變數。 這種行為最常見的例子是for item in list,其中item會在整個疊代過程中依序取得每個list元素的值。

多重賦值是指在單一敘述中,把多個變數賦值給解包後的值的能力。 這能讓程式碼更精簡、更好讀,做法是用逗號分隔要賦值的變數,例如first, second, third = (1,2,3)或for index, item in enumerate(iterable)。

特殊運算子*和**經常用於解包的場合。 *可以用來把多個list/tuple合併成一個list/tuple,做法是把每一個都_解包_到新的共同list/tuple裡。 **可以用來把多個字典合併成一個字典,做法是把每一個都_解包_到新的共同dict裡。

當*運算子沒有搭配集合使用時,它會把若干個值_打包_成一個list。 這在多重賦值中很常用,用來把所有沒有個別賦值的「剩餘」元素集中到單一變數裡。

在 Python 中,使用或定義可接受任意數量位置引數或關鍵字引數的函式時,也很常利用這種解包/打包行為。 你經常會看到這些「特殊」參數定義成def some_function(*args, **kwargs),而這些「特殊」引數則這樣使用:some_function(*some_tuple, **some_dict)。

Caution

*<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中的元素。

解包

Note

下面的範例使用lists,但同樣的概念也適用於tuples。

在 Python 中,可以把list/tuple/dict的元素解包到不同的變數裡。 由於值在lists/tuples中是以特定順序出現的,它們會依相同順序解包到變數中:

>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"

如果有不需要的值,可以用_標記:

>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"

深層解包

從list或tuple內部的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/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()會產生一個包含鍵-值配對的可疊代檢視。 這些配對可以解包成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。 這會把所有值打包成一個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']

# A list literal can be used instead, but might not be as readable.
>>> [*combined_fruits_too] = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']

關於*和**用法的更多細節,請參考PEP 3132和PEP 448。

使用**打包字典

打包字典是使用**運算子來完成。 這會把一個字典中所有的鍵-值配對打包到另一個字典,或把兩個字典合併在一起。

>>> 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用來打包任意數量的位置(非關鍵字)引數, **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之前。

Caution

引數必須按照這樣的結構排列:

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()函式接受多個可疊代物件,並回傳由tuples組成的list,其中每個iterable的值會分組在一起:

>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]
透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 解包與多重賦值

練習已鎖定

再解鎖 3 個練習,就能練習 解包與多重賦值