Use kw_only dataclass fields to prevent argument swaps
Also published in our Blogger archive.
Use kw_only dataclass fields to prevent argument swaps
Positional arguments are compact, but they can hide a swap when adjacent values share a type. An endpoint’s port and timeout are both integers, so Endpoint("api", 5, 443) looks plausible while carrying the wrong meaning. Marking those dataclass fields with field(kw_only=True) makes the generated initializer require their names. The call site therefore records whether 443 is port and whether 5 is timeout_seconds.
The valid construction is asserted and printed. The second construction supplies the two integer values positionally; it raises TypeError, which the example catches to demonstrate the generated signature’s runtime enforcement. This feature prevents accidental positional swapping at that constructor boundary, not every possible configuration mistake. A caller can still deliberately attach an unsuitable number to a correct keyword, so domain validation remains separate. Keyword-only fields are also omitted from the dataclass’s positional pattern-matching arguments, which may affect matching code. Per-field kw_only and the decorator option were added in Python 3.10.
See the official dataclasses.field documentation. AI assistance disclosure: this article was drafted with AI assistance and checked using a synthetic example.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Endpoint:
host: str
port: int = field(kw_only=True)
timeout_seconds: int = field(kw_only=True)
endpoint = Endpoint("api", port=443, timeout_seconds=5)
assert endpoint == Endpoint("api", port=443, timeout_seconds=5)
try:
Endpoint("api", 443, 5)
except TypeError:
print("positional values rejected")
else:
raise AssertionError("keyword-only fields accepted positional values")
print(f"{endpoint.host}:{endpoint.port} timeout={endpoint.timeout_seconds}")
positional values rejected
api:443 timeout=5