We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modelling Data with Dataclasses step 7 of 28
Tell Fields Apart from Class Variables
Your team documents its settings classes with a script that lists every annotated name in a dataclass. Not every annotated name is a field. One belongs to the class, one is only a constructor argument, and one is a marker:
from dataclasses import KW_ONLY, InitVar, dataclass, fields
from typing import ClassVar
@dataclass
class Config:
timeout: float = 5.0
retries = 3
version: ClassVar[str] = "1.0"
seed: InitVar[int] = 0
_: KW_ONLY
debug: bool = False
print([f.name for f in fields(Config)]) # ['timeout', 'debug']
Write a report that lists every annotated name with its kind:
solve("Config")
# [
# ("timeout", "field"),
# ("version", "classvar"),
# ("seed", "initvar"),
# ("_", "pseudo"),
# ("debug", "field"),
# ]
Four kinds of annotated name
- A field is a value that each object stores. The dataclass puts it
in
__init__, the repr and==. ClassVar[str]marks a class variable, a value that belongs to the class and that all instances share, such as a constant or a counter. The dataclass leaves it out of__init__, the repr and==.InitVar[int]marks an argument that__init__passes to__post_init__and does not store. The exercise Compute Cents from an Amount with post_init used one.KW_ONLYis the marker that makes the fields after it keyword-only. You met it in the exercise Fix a Dataclass Hierarchy with Keyword-Only Fields. It is not a field.
retries = 3 has no annotation, so it is a plain class attribute, and the
report leaves it out.
What to write
Fill in field_signature(cls), which returns a list of (name, kind)
tuples:
- Get the real field names from
fields(cls). - Walk
reversed(cls.__mro__).cls.__mro__lists the class and its bases in the order Python searches them, so the reversed walk starts atobjectand ends atcls. - For each class, read its own annotations with
inspect.get_annotations. - Classify each name with the table below, and store its kind in a dictionary. If a later class declares a name again, replace its kind but keep its first position.
- Return the dictionary's items as a list of tuples.
Check the rows in this order, and use the first one that matches:
| Annotation or name | Kind |
|---|---|
The annotation is KW_ONLY |
"pseudo" |
It is InitVar itself, or an instance of InitVar |
"initvar" |
It is ClassVar itself, or get_origin(annotation) is ClassVar |
"classvar" |
| The name is one of the real field names | "field" |
| None of the above | "other" |
The test classes use the first four kinds. Do not parse source code, and
do not create instances. Every annotation on the test classes is a real
object, not a string, so you do not need to resolve strings. Keep solve
and the classes as they are, and return tuples, not lists.
Follow the merge order
NC inherits from NA and NB, so its MRO is NC, NA, NB,
NBase, object. The reversed walk meets NB's b before NA's a,
then NC's c:
solve("NC")
# [("b", "field"), ("a", "field"), ("c", "field")]
Sub declares Base.x again with a new default. The report still puts
x first, then y and z, because assigning to an existing dictionary
key keeps its position.
Your code must pass mypy --strict. DataclassInstance is a type that
exists only for the type checker and means "any dataclass instance". That
is why the starter imports it under if TYPE_CHECKING: and writes it in
quotes. Keep both as they are.
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.