本文 AI 產出,尚未審核
Python 資料結構 – 元組(tuple)
主題:命名元組(collections.namedtuple)
簡介
在 Python 中,元組(tuple)是一種不可變(immutable)的有序集合,常被用來保存「一次性」的資料組合。雖然普通的元組語法簡潔,但在日常開發裡,我們常會遇到「這個位置到底代表什麼?」的困惑——例如 person = ("Alice", 25, "Taipei"),僅靠索引 person[0]、person[1] 來存取資料,程式的可讀性與可維護性會大打折扣。
collections.namedtuple 正是為了解決這個問題而設計的。它在保留元組 不可變、輕量、快速 的特性同時,為每個欄位提供了有意義的名稱,使程式碼更直觀、錯誤更容易被捕捉。對於需要「輕量資料結構」且不想引入完整的類別(class)時,命名元組是理想選擇。
核心概念
1. 為什麼使用 namedtuple?
- 可讀性:欄位名稱代替索引,讓程式更像自然語言。
- 不可變性:與普通元組相同,資料建立後無法被意外修改,降低錯誤風險。
- 記憶體效能:相較於
dict或自訂類別,namedtuple只佔用極少的記憶體。 - 支援全部元組操作:支援切片、迭代、解包等所有元組功能。
2. 建立 namedtuple
from collections import namedtuple
# 建立一個叫做 Person 的命名元組,欄位有 name、age、city
Person = namedtuple('Person', ['name', 'age', 'city'])
- 第一天參數是 類別名稱(僅供顯示),第二個參數是 欄位列表(可以是 list、tuple 或以空格分隔的字串)。
- 建立後
Person本身就是一個 類別,可以直接呼叫產生實例。
3. 建立實例與存取欄位
# 建立實例
alice = Person(name='Alice', age=25, city='Taipei')
# 以屬性方式存取(推薦)
print(alice.name) # Alice
print(alice.age) # 25
# 仍然可以用索引存取(兼容普通元組)
print(alice[2]) # Taipei
4. 常見的 namedtuple 方法
| 方法 | 說明 |
|---|---|
_replace(**kwargs) |
回傳一個新實例,取代指定欄位的值(因為不可變,必須產生新物件) |
_asdict() |
把命名元組轉成 OrderedDict,方便序列化或與 json 互動 |
_fields |
回傳欄位名稱的 tuple |
_make(iterable) |
從可迭代物件建立命名元組(類似構造子) |
5. 程式碼範例
下面提供 五個 常見且實用的範例,示範從基礎到進階的使用方式。每段程式碼皆附有註解說明。
範例 1:基本建立與解包
from collections import namedtuple
# 建立命名元組
Point = namedtuple('Point', 'x y')
p = Point(3, 4)
# 解包成兩個變數
x_coord, y_coord = p
print(f"x = {x_coord}, y = {y_coord}") # x = 3, y = 4
範例 2:使用 _replace 產生新實例
Employee = namedtuple('Employee', ['id', 'name', 'salary'])
bob = Employee(101, 'Bob', 50000)
# 想要調薪 10%,不能直接改屬性,必須產生新物件
bob_new = bob._replace(salary=bob.salary * 1.1)
print(bob) # Employee(id=101, name='Bob', salary=50000)
print(bob_new) # Employee(id=101, name='Bob', salary=55000.0)
範例 3:將命名元組轉成字典(適合 JSON 序列化)
import json
Car = namedtuple('Car', 'brand model year')
my_car = Car('Toyota', 'Corolla', 2020)
car_dict = my_car._asdict() # OrderedDict
json_str = json.dumps(car_dict) # 直接序列化
print(json_str) # {"brand": "Toyota", "model": "Corolla", "year": 2020}
範例 4:使用 _make 從 CSV 行建立物件
import csv
from collections import namedtuple
# 假設有一個 CSV 檔案 people.csv
# name,age,city
# Alice,25,Taipei
# Bob,30,Kaohsiung
Person = namedtuple('Person', 'name age city')
with open('people.csv', newline='', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader) # 跳過表頭
for row in reader:
person = Person._make(row) # 直接從 list 建立
print(person.name, person.age, person.city)
範例 5:結合 typing.NamedTuple(型別提示)
from typing import NamedTuple
class Student(NamedTuple):
id: int
name: str
gpa: float = 0.0 # 預設值
# 建立實例
s1 = Student(1, 'Charlie')
print(s1) # Student(id=1, name='Charlie', gpa=0.0)
# 型別提示讓 IDE 能提供自動完成與錯誤檢查
def honor_roll(students: list[Student]) -> list[Student]:
return [s for s in students if s.gpa >= 3.5]
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 欄位名稱衝突 Python 內建屬性 | 例如使用 class、def、_replace 等作為欄位名稱會被覆蓋或產生錯誤。 |
盡量避免使用保留字或以 _ 開頭的名稱;若必須使用,可在建立時加上 rename=True 讓系統自動改名(會在衝突欄位前加 _)。 |
| 不可變性導致「想改」卻改不掉 | 嘗試直接賦值 obj.field = new_value 會拋出 AttributeError。 |
使用 _replace 產生新實例,或在設計時改用 dataclass(可變版)。 |
| 過度使用命名元組 | 若資料結構需要大量行為(方法)或繼承,命名元組就不再適合。 | 改用 class + @dataclass,或自行實作類別。 |
| 欄位過多時可讀性下降 | namedtuple 的欄位列表過長會讓宣告變得難以閱讀。 |
考慮拆分成多個較小的命名元組,或改用 TypedDict / dataclass。 |
| 序列化時忘記轉成 dict | json.dumps(namedtuple_instance) 會失敗,因為 json 不支援自訂類別。 |
先呼叫 _asdict() 再序列化,或自行實作 default 參數。 |
最佳實踐
- 欄位名稱使用小寫、底線分隔(PEP 8)
Person = namedtuple('Person', 'first_name last_name age') - 在需要預設值時,使用
typing.NamedTuple或namedtuple(..., defaults=…)(Python 3.7+)Point = namedtuple('Point', 'x y', defaults=(0, 0)) - 盡量把「只讀」的資料放在命名元組,避免在需要變更的情境中使用。
- 結合型別提示(
typing.NamedTuple)提升 IDE 支援與程式安全性。 - 使用
__slots__(在namedtuple裡自動實現)避免意外加入屬性,維持記憶體效能。
實際應用場景
| 場景 | 為什麼選擇 namedtuple |
|---|---|
| 資料庫查詢結果 | 查詢返回的每筆紀錄可用 namedtuple 表示,讓欄位名稱直接對應資料表欄位,且不會因為不慎修改資料而破壞原始快照。 |
| 設定檔或參數集合 | 輕量的設定結構(例如 Config = namedtuple('Config', 'host port debug'))不需要額外的類別或 dict,且可保證不可變。 |
| 事件或訊息傳遞 | 在訊息佇列(RabbitMQ、Kafka)中傳遞的訊息結構,用 namedtuple 包裝後更易於讀寫且佔用記憶體少。 |
| 圖形座標、向量運算 | 如 Point(x, y)、Vector(dx, dy),頻繁產生大量小物件時,namedtuple 的效能優於 dict。 |
| 測試資料(fixture) | 單元測試中常需要固定的測試資料,使用 namedtuple 可快速建立且保持不可變,避免測試間相互干擾。 |
總結
collections.namedtuple 為 Python 提供了一個 輕量、可讀、不可變 的資料容器。它結合了普通元組的速度與記憶體效益,同時賦予每個欄位有意義的名稱,讓程式碼更直觀、錯誤更易被捕捉。
- 建立方式:
namedtuple('ClassName', ['field1', 'field2']) - 核心方法:
_replace,_asdict,_fields,_make - 最佳實踐:避免保留字欄位、使用型別提示、在需要預設值時使用
defaults或typing.NamedTuple。
在日常開發中,當你需要一個 只讀、結構固定、效能友好 的資料結構時,namedtuple 常常是最簡潔且最具表現力的選擇。掌握它的使用技巧,能讓你的 Python 程式碼在可讀性與效能之間取得更好的平衡。祝你在實務專案中玩得開心!