Classes vs. dicts
The same data, two different shapes
Every dict you’ve built so far — an agent config, a tool call, a response payload — could just as easily be represented as a class instance instead. Same data, two different containers:
Both work. Both hold the same three values. The question this section answers isn’t “which one is correct” — it’s which one fits a given situation better, and why.
Where a dict wins
A dict is the right shape when the set of keys is genuinely dynamic —
unknown ahead of time, varying between instances, or coming directly from
somewhere you don’t control the shape of (a parsed JSON API response, for
instance). You’ve already used this constantly: **kwargs collects into a
dict specifically because the caller’s keyword arguments aren’t knowable
in advance.
A dict also wins for genuinely dynamic access — looking up a value by a key you only have as a variable, not as a name you’d type into code:
field_to_check = "temperature"
print(agent_config.get(field_to_check))No such attribute lookup is this convenient on a class — you'd need
getattr(agent_config, field_to_check) instead, which exists but is
noticeably less idiomatic for this specific case.(not run live — illustrating the tradeoff, not a working demo)
Where a class wins
A class is the right shape once the fields are known and fixed —
AgentConfig always has exactly name, model, and temperature, never
some other set. That fixed shape buys you three concrete things a dict
can’t offer:
Typos become real errors instead of silent bugs. A wrong dict key just
returns None (or raises KeyError, if you’re not using .get()) with no
indication of what went wrong; a wrong attribute name is closer to a
TypeError immediately, and — as covered next — a good editor will often
flag it before you even run the code.
The dict version’s typo produces None — plausible-looking output that’s
actually silently wrong, the kind of bug that surfaces three functions
later with no clear trail back to its cause. The class version fails
immediately, at the exact line the mistake was made.
Autocomplete and inline documentation work. Because AgentConfig has
type-hinted parameters,
an editor like VS Code knows agent_config. has exactly name, model,
and temperature available, and can show that list as you type — a
dict’s keys are just strings, invisible to the editor until runtime.
Behavior lives with the data. A class can bundle methods alongside its fields — a dict is data only, so any related logic has to live in a separate free-floating function that takes the dict as an argument:
The gap neither one closes yet
Notice what AgentConfig does not do: it doesn’t check that name is
actually a string, or that temperature is actually a number. Nothing
stops this:
This runs without complaint —
type hints aren’t enforced at runtime,
and a plain class’s __init__ is really just a set of assignments with no
validation logic of its own. A dict has exactly the same gap; neither
container, on its own, actually checks that the data it’s holding is
correct. Closing that gap — getting type hints to become real runtime
validation — is where the rest of this lesson is headed.
Why is a dict the better fit for **kwargs-style data, like arbitrary keyword arguments collected in a function?
Implement __init__ storing tool_name, output, and success (defaulting to True) as instance attributes, then implement as_dict() to return them as a plain dict with the exact keys shown.