Python typing.assert_never: ทำให้ branch ที่ไม่ควรถึงตรวจได้ทั้ง static และ runtime
พี่โชว์Engineering
เมื่อ dispatch ครบทุกสมาชิกของ Enum แล้ว branch ที่เหลือควรเป็นไปไม่ได้ typing.assert_never ทำให้ type checker เตือนเมื่อเราเพิ่มสถานะใหม่แต่ลืม handler และยัง raise ตอน runtime หากค่าหลุดมาจริงครับ
from enum import Enum
from typing import assert_never
import unittest
class State(Enum):
READY = "ready"
RUNNING = "running"
DONE = "done"
def label(state: State) -> str:
match state:
case State.READY:
return "รอ"
case State.RUNNING:
return "ทำงาน"
case State.DONE:
return "เสร็จ"
assert_never(state)
class ExhaustiveTests(unittest.TestCase):
def test_all_states(self):
self.assertEqual([label(state) for state in State], ["รอ", "ทำงาน", "เสร็จ"])
def test_runtime_guard(self):
with self.assertRaises(AssertionError):
assert_never("unexpected") # type: ignore[arg-type]
if __name__ == "__main__":
unittest.main()
ต้องรัน type checker แยกด้วย
Python runtime ไม่รู้ว่า match ครบหรือไม่ assert_never มีพลังเต็มเมื่อ CI รัน mypy/pyright และชนิดของค่าแคบลงจนเป็น Never ณ จุดนั้น Test ที่วน Enum ช่วยอีกชั้น แต่ถ้าสถานะมาจาก string ภายนอกให้ validate ตอน parse ก่อน อย่าหวังว่า type hint จะกรอง input ให้เอง
Recap
- ใช้กับ union/Enum ที่ต้อง exhaustive
- รัน static type checker ใน CI
- validate input ภายนอกก่อนสร้าง typed value
- เก็บ runtime assertion เป็น safety net ไม่ใช่ parser