Python annotationlib: อ่าน type hint เป็นข้อความโดยไม่ resolve ชื่อทันที
เครื่องมือทำเอกสารหรือ route inventory มักอยากอ่าน type hint แต่ไม่ควรบังคับ import dependency ทุกตัวหรือประเมิน string annotation โดยไม่จำเป็น Python 3.14 มี annotationlib.get_annotations และ Format.STRING เพื่อขอ representation เป็นข้อความ เหมาะกับงานสำรวจที่ยังไม่ต้อง resolve type จริงครับ
WHY: อ่านฉลากก่อนเปิดเครื่อง
เหมือนเช็กป้ายหน้ากล่องอะไหล่ เราอาจต้องรู้ว่าเขียนว่าอะไร แต่ยังไม่ต้องต่อไฟให้เครื่องด้านในทำงาน การขอ annotation เป็น string ลด coupling กับ import graph และช่วยให้ tooling ทำงานได้แม้ชื่อบางตัวไม่มีใน runtime namespace
from __future__ import annotations
import annotationlib
import unittest
def handler(user_id: UserId, limit: int = 10) -> list[Report]:
return []
def annotation_names(obj: object) -> dict[str, str]:
return annotationlib.get_annotations(obj, format=annotationlib.Format.STRING)
class AnnotationTests(unittest.TestCase):
def test_reads_unresolved_names_as_strings(self) -> None:
self.assertEqual(
annotation_names(handler),
{"user_id": "UserId", "limit": "int", "return": "list[Report]"},
)
def test_original_annotations_are_not_mutated(self) -> None:
before = dict(handler.__annotations__)
annotation_names(handler)
self.assertEqual(handler.__annotations__, before)
if __name__ == "__main__":
unittest.main()
HOW: string format ไม่ใช่ type validation
ผลลัพธ์เป็นข้อความสำหรับแสดงหรือจัดทำ index ไม่ได้ยืนยันว่าชื่อ UserId มีอยู่จริงหรือชนิดตรงกับข้อมูล ถ้าต้องทำ dependency injection หรือ runtime validation ต้องเลือก format และ namespace อย่างตั้งใจ พร้อมถือ annotation จากโค้ดที่ไม่ไว้ใจเป็น input ที่ต้องระวัง
อย่าเขียน parser type hint จาก string เพื่อจำลอง type checker เอง เพราะ syntax มี generic, union, alias และ forward reference มากมาย ใช้ไลบรารีที่เข้าใจ typing model หรือเก็บ string ไว้แสดงเท่านั้น ควร pin Python version ของ tooling เพราะพฤติกรรม annotation เปลี่ยนตามรุ่นภาษา
Recap
- ใช้
Format.STRINGเมื่อต้องการ inventory หรือเอกสาร - อย่าตีความ string ว่า type ถูกต้องแล้ว
- แยกขั้น introspection ออกจาก runtime validation
- ไม่แก้
__annotations__ของ object ต้นทาง - ทดสอบ forward reference และ generic ที่โปรเจกต์ใช้จริง