ข้ามไปยังเนื้อหา

Dataclasses

ใน TypeScript คุณมักเขียน class ขึ้นมาแค่เพื่อเก็บข้อมูลแล้วได้ constructor มาฟรี ๆ หรือไม่ก็ใช้ interface คู่กับ object literal ตอนที่ไม่ต้องการ method เลย

ฝั่ง Python ใช้ decorator @dataclass เชื่อมสองอย่างนี้เข้าด้วยกัน คือยังเป็น class เต็มตัว แต่สร้าง __init__, __repr__ และ __eq__ ให้อัตโนมัติจาก field annotation คุณจึงเขียนแค่ field ก็พอ

TypeScript
// TypeScript — class as data holder
class 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)
Python
from dataclasses import dataclass
@dataclass
class 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 ทำงานเหมือน parameter defaults ของ TypeScript สำหรับ mutable defaults (lists, dicts) คุณ ต้อง ใช้ field(default_factory=...) — อย่า assign mutable literal ตรงๆ เพราะทุก instance จะแชร์ออบเจ็กต์เดียวกัน

TypeScript
// TypeScript — parameter defaults
class Config {
constructor(
public host: string = "localhost",
public port: number = 8080,
public tags: string[] = [] // ⚠ shared ref bug possible
) {}
}
// Correct: factory pattern or spread copy
class 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 ?? [])];
}
}
Python
from dataclasses import dataclass, field
@dataclass
class 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'] — ไม่กระทบ cfg2
print(cfg2.tags) # ['prod']

@dataclass(frozen=True) ทำให้ fields ทั้งหมดเป็น read-only หลัง __init__ นี่คือ Readonly<T> ของ TypeScript หรือ const object ยกเว้นว่า Python enforce จริงที่ runtime

TypeScript
// TypeScript — read-only interface
interface 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 error
Python
from 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: ignore
except Exception as e:
print(f"Error: {e}") # cannot assign to field 'r'
from dataclasses import dataclass, field
@dataclass
class Point:
x: float
y: float
def distance_from_origin(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
@dataclass
class 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 demo
p = Point(3.0, 4.0)
print(p)
print(f"Distance: {p.distance_from_origin()}")
print(f"Equal: {Point(1, 2) == Point(1, 2)}")
# Config demo
cfg = Config(tags=["web"])
print(cfg)
cfg.tags.append("api")
print(f"Tags: {cfg.tags}")
# Frozen demo
red = Color(255, 0, 0)
print(red)
print(f"Hashable: {hash(red)}")
try:
red.r = 50 # type: ignore
except Exception as e:
print(f"Caught: {e}")
ทำไม mutable default values (lists, dicts) ต้องใช้ `field(default_factory=...)` ใน dataclass?
`@dataclass(frozen=True)` เพิ่มอะไรเมื่อเทียบกับ `@dataclass` ปกติ?
`@dataclass` สร้าง dunder methods ใดอัตโนมัติ?
จุดประสงค์ของ `__post_init__` ใน dataclass คืออะไร?