Python 資料結構 – 元組(tuple)
主題:解構賦值(Tuple Unpacking)
簡介
在 Python 中,元組(tuple) 是一種不可變(immutable)的序列類型,常被用來一次傳遞多個值。除了直接存取索引外,最常見且最實用的操作之一,就是解構賦值(亦稱「序列解包」)。透過解構賦值,我們可以把元組裡的每個元素直接展開成獨立的變數,讓程式碼更簡潔、可讀性更高。
解構賦值不僅限於元組,同樣適用於 list、set 甚至是自訂的可迭代物件。掌握這項技巧,能讓你在處理函式回傳多值、資料分割、迭代等情境時,寫出更「Pythonic」的程式碼。本文將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你深入了解解構賦值的威力。
核心概念
1. 基本語法
解構賦值的語法非常直觀,只要左側寫上變數名稱,右側提供一個可迭代物件(如 tuple),Python 會自動把元素一一對應:
# 基本的 tuple 解構賦值
point = (3, 5)
x, y = point # x 取得 3, y 取得 5
print(x, y) # 輸出: 3 5
重點:左側變數的個數必須與右側可迭代物件的元素數量相同,否則會拋出
ValueError。
2. 使用星號(*)收集剩餘元素
有時我們只關心前幾個元素,或想把「剩下的」全部收集到一個 list 中,這時就可以使用 星號運算子 *:
# 把多餘的元素收集到 one_list 中
numbers = (1, 2, 3, 4, 5)
first, second, *rest = numbers
print(first) # 1
print(second) # 2
print(rest) # [3, 4, 5]
*rest必須放在左側變數的 最後,或在 中間(但只能有一個星號變數)。- 收集的結果永遠是 list,即使原始資料是 tuple。
3. 解構嵌套結構
當元組內部還包含其他序列時,我們可以一次解構多層結構:
# 嵌套解構
person = ("Alice", (25, "女"))
name, (age, gender) = person
print(name) # Alice
print(age) # 25
print(gender) # 女
這種寫法在處理 JSON、CSV 或 資料庫查詢結果 時特別有用,能直接把欄位對應到變數。
4. 與函式回傳值結合
函式可以同時回傳多個值(實際上是回傳一個 tuple),配合解構賦值即可直接取得每個結果:
def min_max(nums):
"""回傳列表中的最小值與最大值"""
return min(nums), max(nums)
values = [4, 7, 2, 9, 5]
minimum, maximum = min_max(values)
print(f"最小值: {minimum}, 最大值: {maximum}")
# 輸出: 最小值: 2, 最大值: 9
5. 交換變數的技巧(無需暫存變數)
在其他語言中,交換兩個變數通常需要額外的暫存變數;在 Python,利用 tuple 解構賦值即可一行搞定:
a = 10
b = 20
a, b = b, a # 直接交換
print(a, b) # 20 10
此技巧在演算法(如排序)或 迭代器 中頻繁出現,寫法簡潔且不會產生副作用。
程式碼範例(實用示例)
以下提供 5 個常見且實務導向的範例,說明解構賦值在不同情境下的應用。
範例 1:同時讀取字典的鍵值對
# 把 dict 的 items 直接解構成 key, value
scores = {"Tom": 85, "Jane": 92, "Mike": 78}
for name, grade in scores.items():
print(f"{name} 的成績是 {grade}")
# 輸出:
# Tom 的成績是 85
# Jane 的成績是 92
# Mike 的成績是 78
註解:
dict.items()會回傳(key, value)形式的 tuple,直接解構讓迴圈更直觀。
範例 2:一次取得檔案路徑的目錄與檔名
import os
path = "/home/user/data/report.csv"
directory, filename = os.path.split(path)
print(f"目錄: {directory}")
print(f"檔名: {filename}")
# 輸出:
# 目錄: /home/user/data
# 檔名: report.csv
使用 os.path.split 回傳的 tuple,配合解構賦值即可一次得到兩個資訊。
範例 3:從 API 回傳的 JSON 中抽取特定欄位
import json
response = '{"id": 123, "name": "Widget", "price": 19.99, "stock": 57}'
data = json.loads(response)
# 只取出 id, name, price
product_id, product_name, product_price = data["id"], data["name"], data["price"]
print(product_id, product_name, product_price)
# 輸出: 123 Widget 19.99
若資料結構較複雜,可先建立 tuple 再一次解構,保持程式碼的可讀性。
範例 4:使用 * 收集不定長度的參數
def stats(*numbers):
"""回傳最小值、最大值與其餘所有數字的平均值"""
if not numbers:
return None
minimum, maximum, *rest = numbers
avg_rest = sum(rest) / len(rest) if rest else 0
return minimum, maximum, avg_rest
print(stats(3, 7, 2, 9, 5))
# 輸出: (3, 7, 5.333333333333333)
*rest 讓函式能接受任意長度的參數,同時仍能直接取出前兩個值。
範例 5:在排序演算法中同時交換元素
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
# 直接交換,不需要暫存變數
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
print(bubble_sort([5, 2, 9, 1, 5, 6]))
# 輸出: [1, 2, 5, 5, 6, 9]
此寫法在 排序、排列等演算法中非常常見,既簡潔又避免了多餘的暫存。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 元素數量不匹配 | 左側變數少於或多於右側元素時會拋 ValueError。 |
使用 * 收集多餘元素或確認資料長度。 |
| 星號變數位置錯誤 | *var 只能出現在左側一次,且不能同時放在最左或最右兩端(除非只有一個)。 |
確認星號變數唯一且位置正確。 |
| 可迭代物件不是 tuple/list | 某些自訂物件未實作 __iter__,會導致解構失敗。 |
確保物件實作 __iter__ 或先轉成 list/tuple。 |
| 意外改變可變物件 | 解構賦值本身不會改變原始資料,但若使用 *rest 收集到 list,後續修改會影響該 list。 |
如需保持不變,使用 tuple(rest) 轉為不可變。 |
| 過度解構降低可讀性 | 一行解構太多層次會讓程式碼難以閱讀。 | 盡量保持解構層級在 2 層以內,必要時分步解構。 |
最佳實踐:
- 明確命名:變數名稱要能說明其意圖,例如
x, y = point而非a, b。 - 使用
*收集:當不確定資料長度時,利用*防止ValueError。 - 保持簡潔:一次解構不超過 3~4 個變數,過長的解構應拆成多行。
- 結合函式回傳:設計函式時,直接回傳 tuple,讓呼叫端可以使用解構賦值取得多個結果。
- 避免對
*rest直接修改:若需要保留原始資料,先做淺拷貝或轉為 tuple。
實際應用場景
資料前處理
在機器學習或資料分析前,常需要把 CSV 檔的每一列拆成id, features, label,解構賦值讓程式碼一目了然。網路通信
解析 HTTP 回傳的status_code, headers, body,直接解構成三個變數,方便後續判斷與處理。多值回傳的 API
設計自訂函式或類別方法時,回傳(result, error_msg),呼叫端可直接result, err = func(),不必額外檢查索引。演算法實作
如快速排序、堆疊/佇列操作,常用解構來同時取得元素與剩餘結構,提升效能與可讀性。日誌與除錯
把timestamp, level, message解構後直接寫入日誌檔,省去索引的繁瑣。
總結
解構賦值是 Python 中一項 簡潔且功能強大 的語法糖,讓我們能夠:
- 快速展開 tuple、list 或其他可迭代物件。
- 靈活收集 多餘元素 (
*rest)。 - 直接交換 變數而不需要暫存。
- 提升程式碼可讀性,特別在函式回傳多值、資料前處理與演算法實作時。
掌握這項技巧後,你將在日常開發中減少大量的索引與暫存程式碼,寫出更 Pythonic、更易維護的程式。記得遵守最佳實踐,避免常見陷阱,讓解構賦值成為你手中最得心應手的工具吧!