# Python Dataclasses

> When to activate: dataclasses, attrs, msgspec, frozen dataclasses, slots, inheritance, value objects

- Skill: `mattakushi432/python-dataclasses` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/python-dataclasses`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/python-dataclasses/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/python-dataclasses

---


# Python Dataclasses and Value Objects

## Standard Dataclasses
```python
from dataclasses import dataclass, field, KW_ONLY, replace
from datetime import datetime
from uuid import UUID, uuid4

@dataclass(frozen=True, slots=True)  # frozen=immutable, slots=memory efficient
class Money:
    amount: int  # always store as smallest unit (cents)
    currency: str = "USD"
    
    def __post_init__(self) -> None:
        if self.amount < 0:
            raise ValueError(f"Money amount cannot be negative: {self.amount}")
        if len(self.currency) != 3:
            raise ValueError(f"Currency must be 3-letter ISO code: {self.currency}")
    
    def add(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies")
        return Money(self.amount + other.amount, self.currency)
    
    def __str__(self) -> str:
        return f"{self.amount / 100:.2f} {self.currency}"

@dataclass
class Order:
    id: UUID = field(default_factory=uuid4)
    items: list["OrderItem"] = field(default_factory=list)
    created_at: datetime = field(default_factory=datetime.utcnow)
    _: KW_ONLY  # everything below must be keyword-only
    user_id: int = 0
    
    def total(self) -> Money:
        if not self.items:
            return Money(0)
        return sum((item.subtotal() for item in self.items[1:]), self.items[0].subtotal())

# Update immutable dataclass (returns new instance)
updated_order = replace(order, user_id=42)
```

## msgspec for High-Performance Serialization
```python
import msgspec

class UserEvent(msgspec.Struct, frozen=True):
    user_id: int
    event_type: str
    timestamp: datetime
    metadata: dict[str, str] = {}

# Encode/decode
event = UserEvent(user_id=1, event_type="login", timestamp=datetime.utcnow())
encoded = msgspec.json.encode(event)     # bytes, much faster than json.dumps
decoded = msgspec.json.decode(encoded, type=UserEvent)

# Validation
try:
    bad = msgspec.json.decode(b'{"user_id": "not-an-int"}', type=UserEvent)
except msgspec.ValidationError as e:
    print(e)  # user_id: expected int, got str
```

## attrs (for complex cases)
```python
import attrs

@attrs.define(frozen=True)
class Address:
    street: str = attrs.field(validator=attrs.validators.min_len(1))
    city: str
    country: str = attrs.field(validator=attrs.validators.in_(["US", "CA", "GB"]))
    postal_code: str = attrs.field()
    
    @postal_code.validator
    def _validate_postal_code(self, attribute, value: str) -> None:
        if not value.replace("-", "").isalnum():
            raise ValueError(f"Invalid postal code: {value}")

# Evolution pattern
@attrs.define
class Config:
    host: str = "localhost"
    port: int = 8080
    timeout: float = 30.0
    
    @classmethod
    def from_env(cls) -> "Config":
        import os
        return cls(
            host=os.getenv("HOST", "localhost"),
            port=int(os.getenv("PORT", "8080")),
        )
```

