We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Precise Type Hints with Literals and Generics step 7 of 26
Give Each Task an ID from a ClassVar Counter
A to-do app numbers its tasks 1, 2, 3 as you create them. The next number
lives on the class, because every task shares it. Each task's tags live on
the task itself. You write the Task dataclass, and a solve that reports
what happened.
assert solve(["a", "b", "c"], ["low", "invalid", "high"], "urgent") == (
[1, 2], ["name", "priority", "tags", "id"], ["urgent"], [], ["b"],
)
Task "b" has an invalid priority, so creating it raises ValueError and
uses no ID. Task "c" gets ID 2.
ClassVar and two kinds of field
from dataclasses import dataclass, field, fields
from typing import ClassVar
@dataclass
class Task:
PRIORITIES: ClassVar[tuple[str, ...]] = ("low", "normal", "high")
_next_id: ClassVar[int] = 1
name: str
priority: str = "normal"
tags: list[str] = field(default_factory=list)
id: int = field(init=False, default=0)
def __post_init__(self) -> None:
...
ClassVarmarks an attribute of the class itself, shared by every instance.@dataclassleaves it out of the fields, so it is not a parameter ofTask(...).field(default_factory=list)callslist()for each new task, so every task gets its own empty list.@dataclassraisesValueErrorfor a plaintags: list[str] = [], because all tasks would share that one list.field(init=False, default=0)keepsida field but leaves it out of the parameters ofTask(...).__post_init__is a method that@dataclasscalls at the end of the__init__it writes for you.
A later track covers dataclasses in depth.
What to write
In __post_init__:
- If
self.priorityis not exactly one ofTask.PRIORITIES, raiseValueError. Matching is case-sensitive, and any message will do. - Only after that check passes, set
self.id = Task._next_id, then add 1 toTask._next_id. A rejected task uses no ID. - Update the counter through
Task, notself.self._next_id += 1creates a new attribute on that one task and leaves the class counter unchanged. mypy reports it asCannot assign to class variable "_next_id" via instance. - Names may repeat, and a name may be empty.
solve(names: list[str], priorities: list[str], tag: str) returns
tuple[list[int], list[str], list[str], list[str], list[str]]:
- First set
Task._next_id = 1. The grader loads your code once and runs a batch of test cases against it. Without this reset, the IDs would continue from the previous case. - Pair names with priorities using
zip(names, priorities, strict=True), and create aTaskfor each pair. If the lists differ in length, letzipraise itsValueError. - Catch the
ValueErrorfrom eachTask(...)call, record the rejected name, and continue. - Append
tagonce to the first task you created, if there is one.
The five lists are:
| Position | Contents |
|---|---|
| 1 | IDs of the created tasks, in input order |
| 2 | [f.name for f in fields(Task)] |
| 3 | The first created task's tags, or [] if there is none |
| 4 | The last created task's tags, or [] if there is none |
| 5 | Names of the rejected tasks, in input order |
Position 2 is always ["name", "priority", "tags", "id"]. The two
ClassVar attributes are missing because they are not fields.
Edge cases
- With exactly one created task, the first and the last task are the same
task, so positions 3 and 4 are both
[tag]. - With two or more, the last task's tags stay
[]. - With no created tasks, positions 1, 3 and 4 are
[], and position 2 still lists the field names.
assert solve(["only"], ["normal"], "hot") == (
[1], ["name", "priority", "tags", "id"], ["hot"], ["hot"], [],
)
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.