Typing & Protocols
TypeScript interfaces → Python Protocols
หัวข้อที่มีชื่อว่า “TypeScript interfaces → Python Protocols”ใน TypeScript interface กำหนด contract ออบเจ็กต์ใดก็ตามที่มี shape ที่ถูกต้องก็ตรงตาม interface นั้น — นี่เรียกว่า structural typing Protocol ของ Python (จากโมดูล typing) เป็นแนวคิดเดียวกันทุกประการ: คลาสหนึ่งตรงตาม Protocol ถ้ามีเมธอดและ attributes ที่ถูกต้อง โดยไม่สนว่า inherit มาจากอะไร
// TypeScript structural interfaceinterface Drawable { draw(): string;}
function render(shape: Drawable): string { return shape.draw();}
class Circle { draw() { return "Drawing Circle"; }}
class Square { draw() { return "Drawing Square"; }}
console.log(render(new Circle())); // "Drawing Circle"console.log(render(new Square())); // "Drawing Square"from typing import Protocol
class Drawable(Protocol): def draw(self) -> str: ...
def render(shape: Drawable) -> str: return shape.draw()
class Circle: def draw(self) -> str: return "Drawing Circle"
class Square: def draw(self) -> str: return "Drawing Square"
print(render(Circle())) # Drawing Circleprint(render(Square())) # Drawing Squareสังเกตว่า Circle กับ Square ไม่ได้ inherit จาก Drawable เลย ทั้งคู่แค่มี method draw() ที่ signature ตรงเท่านั้น ซึ่งพอแล้ว ส่วนงานจับข้อผิดพลาดเป็นหน้าที่ของ static analysis tool อย่าง mypy และ pyright
Generics: TypeVar และ Generic
หัวข้อที่มีชื่อว่า “Generics: TypeVar และ Generic”TypeScript generics แมปโดยตรงกับ TypeVar + Generic ของ Python
// TypeScript generic classclass Stack<T> { private items: T[] = []; push(item: T): void { this.items.push(item); } pop(): T { return this.items.pop()!; } peek(): T { return this.items[this.items.length - 1]; }}
const stack = new Stack<number>();stack.push(1);stack.push(2);console.log(stack.pop()); // 2from typing import TypeVar, Generic
T = TypeVar('T')
class Stack(Generic[T]): def __init__(self) -> None: self._items: list[T] = []
def push(self, item: T) -> None: self._items.append(item)
def pop(self) -> T: return self._items.pop()
def peek(self) -> T: return self._items[-1]
stack: Stack[int] = Stack()stack.push(1)stack.push(2)print(stack.pop()) # 2ตั้งแต่ Python 3.9+ คุณสามารถเขียน list[T] ได้โดยตรงในคลาสโดยไม่ต้อง import List จาก typing และตั้งแต่ 3.12 มี syntax type T = ... เพิ่มเข้ามา แต่ TypeVar ยังเป็นรูปแบบที่ compatible กว้างที่สุด
TypedDict: typed dictionaries
หัวข้อที่มีชื่อว่า “TypedDict: typed dictionaries”TypedDict เป็น interface ของ TypeScript ที่อธิบาย plain object (ไม่มีเมธอด) เหมาะมากสำหรับข้อมูลแบบ JSON
// TypeScript interface for a plain objectinterface UserRecord { name: string; age: number; email?: string; // optional key}
const user: UserRecord = { name: "Alice", age: 30 };console.log(user.name);from typing import TypedDict, NotRequired
class UserRecord(TypedDict): name: str age: int email: NotRequired[str] # optional key
user: UserRecord = {"name": "Alice", "age": 30}print(user["name"]) # Alice
# mypy/pyright จะจับ key ที่ขาดหาย:# bad: UserRecord = {"name": "Alice"} # age missingOptional และ Union
หัวข้อที่มีชื่อว่า “Optional และ Union”TypeScript ใช้ string | null | undefined สำหรับ nullable types Python ใช้ Optional[T] (ซึ่งคือ T | None) หรือ union syntax สมัยใหม่ T | None (Python 3.10+)
// TypeScriptfunction greet(name: string | null): string { if (name === null) return "Hello, stranger!"; return `Hello, ${name}!`;}
// Modern TS nullable shorthandfunction greet2(name?: string): string { return name ? `Hello, ${name}!` : "Hello, stranger!";}from typing import Optional
# Optional[str] คือ str | Nonedef greet(name: Optional[str] = None) -> str: if name is None: return "Hello, stranger!" return f"Hello, {name}!"
# Python 3.10+ union syntaxdef greet_modern(name: str | None = None) -> str: return f"Hello, {name}!" if name else "Hello, stranger!"
print(greet()) # Hello, stranger!print(greet("Alice")) # Hello, Alice!ลองเล่น
หัวข้อที่มีชื่อว่า “ลองเล่น”from typing import Protocol, TypeVar, Generic, TypedDict, Optional
# Protocol — structural typingclass Serializable(Protocol): def to_json(self) -> str: ...
class User: def __init__(self, name: str, age: int) -> None: self.name = name self.age = age def to_json(self) -> str: return f'{{"name": "{self.name}", "age": {self.age}}}'
class Product: def __init__(self, title: str) -> None: self.title = title def to_json(self) -> str: return f'{{"title": "{self.title}"}}'
def export_data(item: Serializable) -> str: return item.to_json()
print(export_data(User("Alice", 30)))print(export_data(Product("Widget")))
# Generic StackT = TypeVar('T')
class Stack(Generic[T]): def __init__(self) -> None: self._items: list[T] = [] def push(self, item: T) -> None: self._items.append(item) def pop(self) -> T: return self._items.pop()
s: Stack[str] = Stack()s.push("hello")s.push("world")print(s.pop())
# TypedDictclass Config(TypedDict): host: str port: int
cfg: Config = {"host": "localhost", "port": 8080}print(f"Connecting to {cfg['host']}:{cfg['port']}")
# Optionaldef find_user(user_id: int) -> Optional[str]: db = {1: "Alice", 2: "Bob"} return db.get(user_id)
print(find_user(1)) # Aliceprint(find_user(99)) # NoneLoading Python runtime (first run only)…