Dataclasses
ปัญหาที่ dataclasses แก้ไข
หัวข้อที่มีชื่อว่า “ปัญหาที่ dataclasses แก้ไข”ใน TypeScript คุณมักเขียน class ขึ้นมาแค่เพื่อเก็บข้อมูลแล้วได้ constructor มาฟรี ๆ หรือไม่ก็ใช้ interface คู่กับ object literal ตอนที่ไม่ต้องการ method เลย
ฝั่ง Python ใช้ decorator @dataclass เชื่อมสองอย่างนี้เข้าด้วยกัน คือยังเป็น class เต็มตัว แต่สร้าง __init__, __repr__ และ __eq__ ให้อัตโนมัติจาก field annotation คุณจึงเขียนแค่ field ก็พอ
// TypeScript — class as data holderclass Point { constructor( public x: number, public y: number ) {}
toString(): string { return `Point(${this.x}, ${this.y})`; }}
const p = new Point(3, 4);console.log(p); // Point { x: 3, y: 4 }console.log(p.toString()); // Point(3, 4)from dataclasses import dataclass
@dataclassclass Point: x: float y: float
def distance_from_origin(self) -> float: return (self.x ** 2 + self.y ** 2) ** 0.5
p = Point(3.0, 4.0)print(p) # Point(x=3.0, y=4.0)print(p.distance_from_origin()) # 5.0
# __eq__ ถูกสร้างอัตโนมัติprint(Point(1, 2) == Point(1, 2)) # Trueคุณได้ __init__, __repr__, และ __eq__ มาฟรีโดยไม่ต้องเขียน boilerplate decorator @dataclass อ่าน field annotations ตอน class definition time และสร้างเมธอดเหล่านั้นให้
Default values และ field()
หัวข้อที่มีชื่อว่า “Default values และ field()”Default values ทำงานเหมือน parameter defaults ของ TypeScript สำหรับ mutable defaults (lists, dicts) คุณ ต้อง ใช้ field(default_factory=...) — อย่า assign mutable literal ตรงๆ เพราะทุก instance จะแชร์ออบเจ็กต์เดียวกัน
// TypeScript — parameter defaultsclass Config { constructor( public host: string = "localhost", public port: number = 8080, public tags: string[] = [] // ⚠ shared ref bug possible ) {}}
// Correct: factory pattern or spread copyclass SafeConfig { host: string; port: number; tags: string[]; constructor(opts: Partial<SafeConfig> = {}) { this.host = opts.host ?? "localhost"; this.port = opts.port ?? 8080; this.tags = [...(opts.tags ?? [])]; }}from dataclasses import dataclass, field
@dataclassclass Config: host: str = "localhost" port: int = 8080 # field(default_factory=list) ป้องกัน shared-ref bug tags: list[str] = field(default_factory=list)
cfg1 = Config()cfg2 = Config(port=9000, tags=["prod"])
print(cfg1) # Config(host='localhost', port=8080, tags=[])print(cfg2) # Config(host='localhost', port=9000, tags=['prod'])
cfg1.tags.append("dev")print(cfg1.tags) # ['dev'] — ไม่กระทบ cfg2print(cfg2.tags) # ['prod']frozen=True: immutable records
หัวข้อที่มีชื่อว่า “frozen=True: immutable records”@dataclass(frozen=True) ทำให้ fields ทั้งหมดเป็น read-only หลัง __init__ นี่คือ Readonly<T> ของ TypeScript หรือ const object ยกเว้นว่า Python enforce จริงที่ runtime
// TypeScript — read-only interfaceinterface Color { readonly r: number; readonly g: number; readonly b: number;}
const red: Readonly<{ r: number; g: number; b: number }> = { r: 255, g: 0, b: 0};// red.r = 100; // TS compile errorfrom dataclasses import dataclass
@dataclass(frozen=True)class Color: r: int g: int b: int
red = Color(255, 0, 0)print(red) # Color(r=255, g=0, b=0)
# frozen=True ทำให้ instance เป็น hashable — ใช้เป็น dict key ได้!palette = {red: "primary red"}print(palette[Color(255, 0, 0)]) # primary red
try: red.r = 100 # type: ignoreexcept Exception as e: print(f"Error: {e}") # cannot assign to field 'r'ลองเล่น
หัวข้อที่มีชื่อว่า “ลองเล่น”from dataclasses import dataclass, field
@dataclassclass Point: x: float y: float
def distance_from_origin(self) -> float: return (self.x ** 2 + self.y ** 2) ** 0.5
@dataclassclass Config: host: str = "localhost" port: int = 8080 tags: list[str] = field(default_factory=list)
@dataclass(frozen=True)class Color: r: int g: int b: int
# Point demop = Point(3.0, 4.0)print(p)print(f"Distance: {p.distance_from_origin()}")print(f"Equal: {Point(1, 2) == Point(1, 2)}")
# Config democfg = Config(tags=["web"])print(cfg)cfg.tags.append("api")print(f"Tags: {cfg.tags}")
# Frozen demored = Color(255, 0, 0)print(red)print(f"Hashable: {hash(red)}")try: red.r = 50 # type: ignoreexcept Exception as e: print(f"Caught: {e}")Loading Python runtime (first run only)…