งูขดต้นฉบับกำลังตรวจประตูทางเลือกครบทุกบาน โดยประตูเกินชุดถูกปิดด้วยสลักเล็ก

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