Dataclasses and Value Objects · practice
Create a First Dataclass
Python can write the boilerplate from the previous lesson. Here is the whole of Result:
Five lines. The __init__, the __repr__, and the __eq__ all exist, and all three know about every attribute, because all three were generated from the same
The bug from the previous lesson is not fixed here. It is unavailable. There is nowhere for the three places to disagree, because there is only one place.
The line at the top
from dataclasses import dataclass is the first import in this course, so it deserves a sentence.
Python ships with a large collection of ready-made tools, called the standard library. They are not available by default, because loading everything into every program would be wasteful and would fill your namespace with hundreds of names. An import line says which ones you want. This one asks for the dataclass tool from the dataclasses collection, and afterwards dataclass is an ordinary name you can use.
That is all you need for this chapter. Chapter 10 covers imports properly, including writing
What each line does
@dataclass
class Result:
learner: str
score: int
total: int
@dataclass above a class is the same shape as @property above a
Inside, learner: str is an annotation: the name of an attribute and the type it is expected to hold. You met annotations on @dataclass reads these lines to find out which attributes exist. It is the only feature in this course where an annotation changes what the program does.
So the
Python builds it without complaint. The annotation records intent and lets a type checker warn you. If a
What is score: int doing inside a dataclass?
What you get, and what you do not
@dataclass generates __init__, __repr__, and __eq__. That is the default, and it is worth knowing exactly where it stops:
No __hash__, for the reason Chapter 7 gave: a dataclass is
There is also no __str__, automatic validation, or ordering unless you ask.
Your own methods still belong there
A dataclass is an ordinary class. Everything you know still applies:
The generated __init__ calls __post_init__ after assigning the fields. That hook is where a dataclass keeps Chapter 5’s construction rules. Generated __init__, __repr__, and __eq__; hand-written validation, is_correct, and __str__. For these methods, a definition you write yourself is kept instead of a generated version.
This is the point of the feature. It removes the lines you were going to type anyway, and leaves the design decisions exactly where they were.
Task
Replace the hand-written machinery with generated machinery, keeping everything that was a real decision.
Result has already been converted and the import is already in place. Use it as the pattern for turning Duration and Question into dataclasses. Each loses its __init__, __repr__, and __eq__, and gains an annotated attribute line for each
Keep every Duration.total_seconds, Question.is_correct, and Question.__str__. Restore Question’s construction rules in __post_init__: prompt and answer must be non-empty, and points must be greater than zero.
Do not add a __hash__. The next lessons explain why that is not simply an oversight.
The behavior and custom display should stay the same. The generated repr now includes field names, such as Question(prompt=..., answer=..., points=...), so that line will change.