dataclasses.KW_ONLY lets a dataclass define a boundary after which fields must be supplied by name. This makes constructors easier to read, prevents ambiguous calls, and gives public APIs more room to evolve without breaking existing code. Instead of relying on a long positional sequence, you can require callers to spell out sensitive or optional parameters.
This guide explains how KW_ONLY works, how it differs from kw_only=True, and how it interacts with inheritance, defaults, __match_args__, structural pattern matching, and dataclasses.replace. It also covers practical design rules and common mistakes.
The positional argument problem
Imagine a configuration class with several booleans and integers. A call such as Config('prod', True, False, 30) is difficult to understand without opening the class definition. Swapping two values of the same type may not raise an exception, yet it can silently change behavior.
A keyword-oriented call is clearer: Config('prod', debug=True, cache=False, timeout=30). The code documents its own intent, and future field reordering is less likely to introduce subtle regressions.
How to use dataclasses.KW_ONLY
KW_ONLY is a type marker. Add a pseudo-field, conventionally named _, and every field declared after it becomes keyword-only.
from dataclasses import dataclass, KW_ONLY
@dataclass
class Service:
name: str
_: KW_ONLY
timeout: int = 30
retries: int = 3
debug: bool = False
api = Service('payments', timeout=10, retries=5)
The marker is not stored on instances, does not appear in the representation, and never receives a value. Its only job is to divide positional fields from named fields.
Calling Service('payments', 10, 5) raises TypeError because timeout and retries must be passed by name.
KW_ONLY versus kw_only=True
The decorator option @dataclass(kw_only=True) makes every field keyword-only. It is a good choice when positional construction does not improve the API. KW_ONLY is more selective: it preserves a few essential positional fields while requiring names for the remaining options.
A useful rule is to keep positional only the fields that clearly identify the object and are unlikely to move. Flags, limits, policies, credentials, and future extension points are usually safer as keyword-only fields.
Required keyword-only fields
Fields after the marker may have defaults, but they do not have to. A required field remains required; callers simply have to name it.
@dataclass
class Connection:
host: str
_: KW_ONLY
token: str
port: int = 443
verify_tls: bool = True
conn = Connection('api.example.com', token='secret')
This is valuable for credentials and security settings. Explicit labels such as token= and verify_tls= reduce mistakes in long calls and make code review easier.
Defaults and constructor design
Keyword-only parameters do not remove the need for good defaults. Defaults should be safe, predictable, and documented. Avoid defaults that perform work, read the environment, or create mutable shared state. Use field(default_factory=...) for lists, dictionaries, and other mutable values.
When a class grows beyond a manageable number of options, split it into smaller configuration dataclasses. KW_ONLY improves a constructor, but it should not be used to justify an object with dozens of unrelated responsibilities.
Inheritance and final field order
Dataclasses merge fields from base and derived classes. Therefore, inheritance can make the final constructor less obvious than the source of one class suggests. A marker in a base class shapes that class, while a subclass can introduce additional fields and its own keyword-only decisions.
Use inspect.signature to verify the public constructor:
from inspect import signature
print(signature(Service))
For a library, a small signature test is useful. It catches accidental changes when fields are reordered or a new base class is introduced.
Pattern matching and __match_args__
Keyword-only fields are not added to __match_args__. As a result, positional structural patterns only include positional dataclass fields. This usually makes matching more stable.
match api:
case Service(name):
print(name)
To inspect keyword-only data, match by attribute: case Service(timeout=10). Attribute patterns are explicit and continue to work if internal field ordering changes.
Using dataclasses.replace
dataclasses.replace works naturally with keyword-only fields because modifications are supplied by name. It is especially useful with immutable models declared using frozen=True.
from dataclasses import replace
fast = replace(api, timeout=5)
The function creates a new instance, changes the requested field, and preserves the remaining state. This supports clear, functional updates without mutation.
Why it helps public APIs
Positional parameters are part of a public contract. Adding a parameter in the middle of a positional signature can break or, worse, reinterpret older calls. Keyword-only optional fields are easier to add because callers identify them by name.
This matters in SDKs, internal libraries, domain models, service clients, and configuration objects that evolve over time. Keyword-only fields do not replace semantic versioning, but they reduce the number of changes that require a breaking release.
Validation still matters
KW_ONLY controls how arguments are passed; it does not validate their values. Use __post_init__ for domain rules.
@dataclass
class Job:
name: str
_: KW_ONLY
priority: int = 5
attempts: int = 3
def __post_init__(self):
if not 1 <= self.priority <= 10:
raise ValueError('priority must be between 1 and 10')
if self.attempts < 0:
raise ValueError('attempts cannot be negative')
The constructor remains readable while validation protects the object state. These techniques solve different problems and work well together.
Common mistakes
Do not add multiple KW_ONLY markers to one dataclass. One boundary is enough. Name the marker _ so readers immediately understand that it is not real data. Avoid making every tiny identifier keyword-only when a simple positional call would be natural.
Another mistake is treating keyword-only fields as a security mechanism. They improve clarity but do not sanitize input, enforce types at runtime, or protect secrets. Validation, type checking, and secure storage remain separate concerns.
Testing the contract
Test successful named calls and expected failures for positional calls. Check required keyword-only fields, default values, inheritance, pattern matching, and replace. For public packages, snapshotting the signature can prevent accidental compatibility changes.
Static type checkers also understand keyword-only constructor parameters generated by dataclasses. Running tools such as mypy or Pyright adds another layer of protection before runtime.
Practical design checklist
Keep only one or two truly essential fields positional. Make options and flags keyword-only. Choose safe defaults. Validate domain rules in __post_init__. Prefer smaller composed models over a giant constructor. Document required fields and test the final signature.
For external data validation, a library such as Pydantic may be more appropriate. For lightweight typed records inside Python applications, dataclasses remain an efficient standard-library choice.
Related reading
Continue with our guides to Python typing.override, Python StrEnum, Python SimpleNamespace, and Python types.new_class. Together, they help you design clearer models and safer contracts.
For primary references, read the official dataclasses documentation and PEP 557.
Conclusion
dataclasses.KW_ONLY is a small feature with a large effect on readability and compatibility. It preserves concise positional arguments where they make sense and requires explicit names for options that could otherwise be confused. In evolving projects, that boundary reduces mistakes, improves reviews, and makes new optional fields easier to introduce. Combine it with validation, signature tests, type checking, and clear documentation for robust dataclass APIs.







