攝影或3C

Python typing.TypedDict vs typing.NamedTuple 教學; from typing import NamedTuple, TypedDict

## 1. 一句話總結

## 2. TypedDict:長得像 dict,但欄位被鎖定

`TypedDict` 讓你在**用字典的同時**
告訴 type checker
「這個 dict 應該有哪些 key、
每個 key 是什麼型別」。

from typing import TypedDict


class Point(TypedDict):
    x: int
    y: int


# 建立時就是一個普通 dict
p: Point = {"x": 1, "y": 2}

print(p["x"])        # 讀取用中括號 → 1
p["x"] = 10          # ✅ 可以修改
print(p)             # {'x': 10, 'y': 2}

### 另一種寫法:函式式建立法

跟 `NamedTuple` 一樣,
`TypedDict` 也有「函式呼叫式」寫法,兩者長得很像:

from typing import NamedTuple, TypedDict

PointNT = NamedTuple("PointNT", [("x", int), ("y", int)])   # list of (名稱, 型別)
PointTD = TypedDict("PointTD", {"x": int, "y": int})        # dict {名稱: 型別}

p: PointTD = {"x": 1, "y": 2}   # 用法和 class 版完全一樣

差別只在第二個參數的容器:
`NamedTuple` 用 **list of tuple**
`TypedDict` 用 **`{欄位: 型別}` dict**

**什麼時候才需要函式式?**
平常優先用 class 版(最好讀、IDE 支援最完整)。

只有欄位名**不是合法 Python 識別字**
(含連字號、空格、數字開頭)時,
class 版寫不出來,才非用函式式:

# "release-year" 有連字號不是合法變數名稱class 版無解只能函式式
Movie = TypedDict("Movie", {"name": str, "release-year": int})

### 好處:打錯 key 會被抓到

p: Point = {"x": 1, "y": 2}

p["z"] = 3           # ⚠️ type checker 會警告:Point 沒有 z 這個 key
value = p["xx"]      # ⚠️ 拼錯 key,type checker 會提醒

> 注意:`TypedDict` 的檢查是**靜態**
(由 Pylance / mypy 等工具在寫程式時提醒),

> 執行期它就是一個普通 dict,Python 本身不會真的擋下你。

### 天生就能轉 JSON

因為它本體是 dict,`json.dumps` 會**保留鍵名**

import json

p: Point = {"x": 1, "y": 2}
print(json.dumps(p))   # {"x": 1, "y": 2}欄位名還在

## 3. NamedTuple:長得像 class,但本質是 tuple

`NamedTuple` 讓你用**欄位名稱**去存取 tuple 的內容,
比 `t[0]`、`t[1]` 好讀很多。

from typing import NamedTuple


class Point(NamedTuple):
    x: int
    y: int


p = Point(x=1, y=2)   # 像呼叫 class 一樣建立

print(p.x)            # 用點存取 → 1
print(p[0])           # 也能用位置 → 1

### 好處:不可變,安全

p = Point(x=1, y=2)

p.x = 10              # ❌ 會直接報錯AttributeErrortuple 不能改

想「改」的話,其實是產生一個新的:

p2 = p._replace(x=10)
print(p)              # Point(x=1, y=2)  ← 原本的沒變
print(p2)             # Point(x=10, y=2) ← 新的

### 適合當函式的多值回傳

class DivResult(NamedTuple):
    quotient: int
    remainder: int


def divide(a: int, b: int) -> DivResult:
    return DivResult(a // b, a % b)


r = divide(17, 5)
print(r.quotient, r.remainder)   # 3 2

# 舊式的位置解包照樣可用
q, rem = divide(17, 5)
print(q, rem)                    # 3 2

## 4. 一個關鍵陷阱:轉 JSON 的差別

這是實務上最容易踩到的點。

import json
from typing import NamedTuple


class Point(NamedTuple):
    x: int
    y: int


p = Point(x=1, y=2)
print(json.dumps(p))   # [1, 2]   ← 欄位名不見了變成陣列

– `NamedTuple` → `json.dumps` 會變成 **JSON 陣列 `[1, 2]`****欄位名遺失**

– `TypedDict` → `json.dumps` 會是 **JSON 物件 `{“x”: 1, “y”: 2}`****欄位名保留**

👉 **只要這份資料要寫進 JSON 檔、之後還要靠鍵名讀回來,就用 `TypedDict`。**

## 5. 該選哪個?決策指南

**選 `TypedDict` 當:**

– 資料本來就是 dict(例如從 JSON 讀進來的)。

– 要 `json.dumps` 寫檔,且欄位名不能掉。

– 想在既有 dict 程式碼上「補上型別」,又不想改存取方式。

**選 `NamedTuple` 當:**

– 函式要一次回傳多個值。

– 資料建立後不該被修改(不可變比較安全)。

– 想用 `t.field` 的清楚寫法取代 `t[0]`、`t[1]`。

## 6. 並排對照

## 7. 防止「錯配」:兩者強度不一樣

很多人以為兩者防呆效果一樣,
其實**執行期強度差很多**
關鍵在:`TypedDict` 的防護幾乎
**全靠 type checker**(靜態),
執行期它就是個普通 dict;
`NamedTuple` 因為不可變 + tuple 本質,
很多錯在**執行期就直接爆**

### 7.1 讀取打錯欄位名

# NamedTuple點存取打錯執行期直接 AttributeError
t.flg          # ❌ Python 本身就擋

# TypedDictkey 打錯執行期不一定擋
d["flg"]       # ⚠️ type checker 警告;執行期是 KeyError
d.get("flg")   # 😶 靜默回 None,最陰險(不會報錯)

### 7.2 少給/多給欄位

# NamedTuple建構參數不對執行期 TypeError
Point(1)              # ❌  y直接爆

# TypedDict少給 key執行期不爆只有 type checker 警告
p: Point = {"x": 1}   # ⚠️ 少 y,靜態警告;執行期照跑

### 7.3 建立後被亂改

t.x = 999      # NamedTuple:❌ 不可變直接報錯
d["x"] = 999   # TypedDict:✅ 可隨便改改成錯的也沒人擋

### 7.4 欄位型別錯(唯一打平的情況)

Point(x="s", y=2)              # NamedTupletype checker 警告,執行期不擋
p: Point = {"x": "s", "y": 2}  # TypedDict:type checker 警告,執行期不擋

型別層級兩者都只靠 type checker,執行期都不驗證,效果相同。

### 防呆對照總表

👉 **論防呆最硬,`NamedTuple` 明顯更強**
(不可變 + 建構/讀取的錯執行期就爆)。

但若要「像 dict 一樣彈性 + 能存 JSON」,
還是選 `TypedDict`,代價是防護幾乎全靠 type checker。

> 補充:`collections.namedtuple`(小寫)沒有型別註解,
欄位一律當 `Any`,連 7.4 的靜態型別警告都拿不到;

> 要防呆請用 `typing.NamedTuple`(class 繼承式)。

## 8. 小結

**要存 JSON、要保留鍵名 → `TypedDict`**(它就是有型別提示的 dict)。

**要固定欄位、不可變、當函式回傳 → `NamedTuple`**(它就是有名字的 tuple)。

– 兩者都只是給人和 type checker 看的「說明書」,
讓程式更好讀、更早抓到錯。

推薦hahow線上學習python: https://igrape.net/30afN

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡