Customize derived behavior (How-to)¶
This page is task-focused: how to get the behavior you want when the default derive behavior doesn’t fit.
For the authoritative catalog of derives/dunders, see:
Custom string output for your type¶
Goal (custom string output)¶
Make println(f"{x}") show a friendly string.
Steps (custom string output)¶
- Define
__str__(self) -> stron yourmodel/class. - Do not also
@derive(Display)(that’s a conflict).
model User:
name: str
email: str
def __str__(self) -> str:
return f"{self.name} <{self.email}>"
Custom equality (== and !=)¶
Goal (custom equality)¶
Compare values by a subset of fields (for example, compare by id only).
Steps (custom equality)¶
- Adopt
Eqfromstd.derives.comparisonand define__eq__(self, other: Self) -> bool. AdoptingEqgives you!=as the negation of__eq__; without it,!=needs its own__ne__. - Do not also
@derive(Eq).
from std.derives.comparison import Eq
model User with Eq:
id: int
name: str
def __eq__(self, other: User) -> bool:
return self.id == other.id
If such a type also derives Hash to be a set element or dict key, keep __eq__ consistent with the hash: two values that __eq__ calls equal must hash alike. A derived hash covers every field, so pair it only with an __eq__ that compares every field. For equality by a subset of fields, key the collection by that subset instead (see Key a set or dict by a custom identity).
Custom ordering (<, <=, >, >=)¶
Goal (custom ordering)¶
Compare values using a domain-specific rule.
Steps (custom ordering)¶
- Adopt
Ordfromstd.derives.comparisonand define__eq__and__lt__(self, other: Self) -> bool. AdoptingOrdgives you<=,>and>=from those two; without it, each operator needs its own dunder. - Do not also
@derive(Ord).
from std.derives.comparison import Ord
model Task with Ord:
priority: int
name: str
def __eq__(self, other: Task) -> bool:
return self.priority == other.priority and self.name == other.name
def __lt__(self, other: Task) -> bool:
if self.priority != other.priority:
return self.priority < other.priority
return self.name < other.name
Comparing a second field when the first ties keeps < consistent with __eq__: two tasks that are not equal are always ordered one way or the other.
See Derives: Comparison → Ord for the ordering rules.
Key a set or dict by a custom identity¶
Goal (custom identity keys)¶
Use values as set elements or dict keys by one part of them (for example, by id only).
Steps (custom identity keys)¶
- Key the collection by the field that carries the identity: a
dict[int, User]keyed byuser.id, or aset[int]of ids. - When every field is part of the identity, add
@derive(Eq, Hash)to the type and use it as the key directly. - Do not rely on a
__hash__method: a set or dict does not call it. A set element or dict key implementsEqandHash, so a type that defines__eq__is a key only when it also adoptsEq(model User with Eq) and derivesHash, and is refused otherwise (INCAN-T0114). A derivedHashcovers every field, so when__eq__compares only part of the value, key the collection by that part as in step 1.
model User:
id: int
name: str
def __eq__(self, other: User) -> bool:
return self.id == other.id
def index_by_id(users: list[User]) -> dict[int, User]:
by_id: dict[int, User] = {}
for user in users:
by_id[user.id] = user
return by_id
See Derives: Comparison → Hash for which types can be set elements and dict keys.
Field defaults: make construction ergonomic¶
Goal (field defaults)¶
Make Type() work when fields have defaults.
Steps (field defaults)¶
- Put defaults on the fields (
field: T = expr). - Any field with no default must still be provided.
model Settings:
theme: str = "dark"
font_size: int = 14