We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modelling Data with Dataclasses step 6 of 28
Fix a Dataclass Hierarchy with Keyword-Only Fields
You model storage resources as frozen dataclasses. Every resource has an
id, a created_at time with a default, and an owner. The module fails
as soon as Python reaches the first class:
from dataclasses import dataclass
@dataclass(frozen=True)
class Resource:
id: str
created_at: str = "1970-01-01T00:00:00Z"
owner: str
# TypeError: non-default argument 'owner' follows default argument
The generated __init__ takes the fields in order, so it would be
__init__(self, id, created_at="1970-...", owner). Python rejects that
for any function, because a parameter without a default cannot follow one
with a default.
Make the fields keyword-only
A keyword-only parameter is one the caller must pass as name=value.
Keyword-only parameters can come in any order, with or without defaults.
@dataclass(kw_only=True) makes every field that the class declares
keyword-only. With it on Resource:
Resource(id="r1", owner="team-a") # works
Resource("r1", "team-a") # TypeError
kw_only=True covers only the fields that its own class declares. A
subclass without it adds ordinary positional fields. So put it on every
class in the hierarchy.
To make only some fields keyword-only, use KW_ONLY. It is a marker that
makes the fields after it keyword-only, and the marker itself is not a
field:
from dataclasses import KW_ONLY, dataclass
@dataclass
class Job:
name: str
_: KW_ONLY
retries: int = 3
owner: str
print(Job("backup", owner="ops")) # Job(name='backup', retries=3, owner='ops')
For this task, put kw_only=True on each decorator instead.
What to write
The starter defines three frozen dataclasses:
Resourcedeclaresid,created_atandowner, in that order. Onlycreated_athas a default,"1970-01-01T00:00:00Z".Bucketinherits fromResourceand adds a requiredname.VersionedBucketinherits fromBucketand adds a requiredversion.
Fix them:
- Add
kw_only=Trueto all three decorators, next tofrozen=True. - Keep the field types, the field order and the default as they are.
- Do not give
owner,nameorversiona default.
Then fill in solve. Build the object that target names with keyword
arguments, and leave created_at at its default:
| target | Construction |
|---|---|
| "Resource" | Resource(id=resource_id, owner=owner) |
| "Bucket" | Bucket(id=resource_id, owner=owner, name=name) |
| "VersionedBucket" | VersionedBucket(id=resource_id, owner=owner, name=name, version=version) |
Raise ValueError for any other target. solve gives name and
version defaults only so that one signature serves all three targets.
The class fields stay required.
Return a dictionary with these keys:
"field_order": the names fromfields(obj), in order."match_args": the class's__match_args__, converted to a list."repr":repr(obj)."positional_error": what the supplied_positional_errorhelper returns. It tries to build the class with one positional argument and returns"TypeError"when that fails.
solve("Bucket", "r1", "team-a", name="logs")
# {
# "field_order": ["id", "created_at", "owner", "name"],
# "match_args": [],
# "repr": "Bucket(id='r1', created_at='1970-01-01T00:00:00Z', owner='team-a', name='logs')",
# "positional_error": "TypeError",
# }
Keyword-only fields keep their declared order in fields() and in the
repr. __match_args__ lists only the fields you can pass by position, so
here it is empty. A class pattern such as Bucket(name=n) still works,
and Bucket(n) raises TypeError.
Your code must pass mypy --strict. The helper stores the class in a
Callable[..., object] variable, so that mypy allows a call that the
class's signature forbids. Keep the helper as it is.
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.