Lesson 11 of 20

Functions & Classes

Dataclasses

3 min read

Dataclasses reduce boilerplate when creating classes that mainly store data. They automatically generate __init__, __repr__, and comparison methods.

Basic Dataclass

from dataclasses import dataclass

@dataclass
class Message:
    role: str
    content: str

# Automatically generates __init__
msg = Message(role="user", content="Hello!")

# Automatically generates __repr__
print(msg)  # Message(role='user', content='Hello!')

# Attribute access works normally
print(msg.role)     # "user"
print(msg.content)  # "Hello!"

Comparison: Regular Class vs Dataclass

# Regular class (verbose)
class MessageOld:
    def __init__(self, role, content):
        self.role = role
        self.content = content

    def __repr__(self):
        return f"Message(role='{self.role}', content='{self.content}')"

    def __eq__(self, other):
        return self.role == other.role and self.content == other.content

# Dataclass (concise)
@dataclass
class Message:
    role: str
    content: str
# All methods above are generated automatically!

Default Values

from dataclasses import dataclass
from typing import Optional

@dataclass
class ChatConfig:
    model: str = "gpt-4"
    temperature: float = 0.7
    max_tokens: int = 1000
    stream: bool = False

# Use defaults
config1 = ChatConfig()

# Override some values
config2 = ChatConfig(model="claude-3-opus", temperature=0.9)

Fields with Default Factory

For mutable defaults (lists, dicts), use field():

from dataclasses import dataclass, field
from typing import List, Dict

@dataclass
class Agent:
    name: str
    model: str = "gpt-4"
    # WRONG: history: List[Dict] = []  # Shared between instances!
    # RIGHT: Use field with default_factory
    history: List[Dict] = field(default_factory=list)
    config: Dict = field(default_factory=dict)

agent1 = Agent(name="Agent1")
agent2 = Agent(name="Agent2")

agent1.history.append({"msg": "Hello"})
print(agent1.history)  # [{"msg": "Hello"}]
print(agent2.history)  # [] (separate list)

Frozen Dataclasses (Immutable)

from dataclasses import dataclass

@dataclass(frozen=True)
class ApiKey:
    provider: str
    key: str

key = ApiKey(provider="openai", key="sk-...")
# key.provider = "anthropic"  # Error! Cannot modify frozen dataclass

Practical AI Example

from dataclasses import dataclass, field
from typing import List, Optional

@dataclass
class ToolCall:
    name: str
    arguments: dict

@dataclass
class AgentResponse:
    content: str
    tool_calls: List[ToolCall] = field(default_factory=list)
    finish_reason: str = "stop"
    usage: Optional[dict] = None

@dataclass
class ConversationTurn:
    user_message: str
    assistant_response: AgentResponse
    timestamp: float = field(default_factory=lambda: __import__('time').time())

# Clean, type-safe data structures
response = AgentResponse(
    content="Let me search for that.",
    tool_calls=[ToolCall(name="search", arguments={"query": "Python"})]
)

Next, we'll learn how to handle errors gracefully in Python. :::

Quiz

Module 3: Functions & Classes

Take Quiz