Metaclasses & Descriptors
ไม่มีคู่เทียบใน TypeScript — และนั่นคือประเด็น
หัวข้อที่มีชื่อว่า “ไม่มีคู่เทียบใน TypeScript — และนั่นคือประเด็น”descriptor กับ metaclass คือสองฟีเจอร์ระดับ runtime ที่ทรงพลังที่สุดของ Python ฝั่ง TypeScript มี decorator อยู่ (stage-3, ยัง experimental) แต่ทำได้จำกัดกว่ามาก
descriptor protocol และระบบ metaclass ของ Python ทำงานในชั้นที่ลึกกว่านั้น คือเข้าไปคุมว่าการเข้าถึง attribute และการสร้าง class เกิดขึ้นอย่างไรตอน runtime เข้าใจสองเรื่องนี้แล้วคุณจะเห็นภาพทันทีว่า @property, @dataclass และ ORM อย่าง SQLAlchemy ทำงานเบื้องหลังอย่างไร
Descriptors: การควบคุมการเข้าถึง attribute
หัวข้อที่มีชื่อว่า “Descriptors: การควบคุมการเข้าถึง attribute”descriptor คือออบเจ็กต์ใดก็ตามที่กำหนด __get__, __set__, หรือ __delete__ เมื่อ descriptor instance ถูก assign เป็น class attribute Python จะเรียก methods เหล่านี้แทนการ lookup ใน dictionary ทุกครั้งที่คุณเข้าถึง attribute บน instance
// TypeScript — closest analogy: getter/setterclass Temperature { private _celsius: number;
constructor(celsius: number) { this._celsius = celsius; }
get fahrenheit(): number { return this._celsius * 9/5 + 32; }
set fahrenheit(value: number) { this._celsius = (value - 32) * 5/9; }}
const t = new Temperature(100);console.log(t.fahrenheit); // 212t.fahrenheit = 32;console.log(t.fahrenheit); // 32# Python — descriptor protocol (reusable across classes)class Validated: """Descriptor: ensures field is numeric.""" def __set_name__(self, owner, name): self._name = name # เรียกตอน class creation
def __get__(self, obj, objtype=None): if obj is None: return self # class-level access return getattr(obj, f"_{self._name}", None)
def __set__(self, obj, value): if not isinstance(value, (int, float)): raise TypeError( f"{self._name} must be numeric, " f"got {type(value).__name__}" ) setattr(obj, f"_{self._name}", value)
class Product: price = Validated() # descriptor instance เป็น class attr quantity = Validated()
def __init__(self, price: float, quantity: int) -> None: self.price = price self.quantity = quantity
def total(self) -> float: return self.price * self.quantity
item = Product(9.99, 3)print(item.total()) # 29.97item.price = "free" # TypeError: price must be numericข้อสังเกตสำคัญ: descriptor Validated สามารถ reuse กับคลาสต่างๆ และ attributes ต่างๆ ได้จำนวนมาก getter/setter ของ TypeScript เป็นแบบ per-class และ per-property — คุณไม่สามารถแชร์ definition เดียวได้
set_name: descriptor self-registration
หัวข้อที่มีชื่อว่า “set_name: descriptor self-registration”Python เรียก __set_name__ ตอนสร้าง class (จังหวะที่ class body รัน) ทำให้ descriptor รู้ชื่อ attribute ที่ตัวเองถูก assign ไปให้ นี่คือเหตุผลที่คุณไม่ต้องเขียน price = Validated("price") เพราะ Python ใส่ชื่อให้เอง
ลองเล่น — descriptor demo
หัวข้อที่มีชื่อว่า “ลองเล่น — descriptor demo”class Validated: def __set_name__(self, owner, name): self._name = name
def __get__(self, obj, objtype=None): if obj is None: return self return getattr(obj, f"_{self._name}", None)
def __set__(self, obj, value): if not isinstance(value, (int, float)): raise TypeError( f"{self._name} must be numeric, got {type(value).__name__}" ) setattr(obj, f"_{self._name}", value)
class Product: price = Validated() quantity = Validated()
def __init__(self, price: float, quantity: int) -> None: self.price = price self.quantity = quantity
def total(self) -> float: return self.price * self.quantity
item = Product(9.99, 3)print(f"Total: {item.total()}")
item.price = 19.99print(f"Updated total: {item.total()}")
try: item.price = "free"except TypeError as e: print(f"Caught: {e}")Loading Python runtime (first run only)…