Back to Blog
Python

Python Dataclass Fields: Syntax, Defaults, and Options

Learn how to declare, configure, and manage python dataclass fields, including defaults, field() options, mutable defaults, slots, and common pitfalls.

dataclassespythontype hintsfield defaultsdata classes
Illustration of Python dataclass fields with typed attributes and default values

Python dataclass fields are the core of the dataclasses module, letting you declare instance attributes with type annotations and automatically generate __init__, __repr__, __eq__, and other methods. Understanding how fields work is essential for writing clean, maintainable data containers. This article focuses on the practical aspects of defining and configuring fields, from basic syntax to advanced options like kw_only and slots.

Declaring Fields with Type Annotations

The simplest way to create a dataclass field is to use a class-level annotation. Each annotated variable becomes a field, and the dataclass decorator uses those annotations to generate the constructor and other methods.

from dataclasses import dataclass @dataclass class Point: x: float y: float

Here x and y are fields. The generated __init__ takes x and y as positional or keyword arguments, and __repr__ shows them in a readable format. The type annotation is not enforced at runtime; it is primarily for static type checkers and documentation. If you need runtime validation, you must implement it separately.

Default Values and field()

Fields can have default values, which makes them optional in the constructor. A default is assigned using a standard assignment expression:

@dataclass class Rectangle: width: float height: float color: str = "black"

Fields with defaults must come after fields without defaults, otherwise Python raises a SyntaxError because the generated __init__ would have non-default arguments after default ones. For more control over a field's behavior, use the field() function from the dataclasses module. field() allows you to set options like default, default_factory, init, repr, compare, hash, and metadata.

from dataclasses import dataclass, field @dataclass class Product: id: int name: str tags: list = field(default_factory=list) price: float = field(default=0.0, compare=False)

The field() function is necessary when you need a mutable default (like a list) or when you want to exclude a field from comparison or representation. The table below summarizes the most common field() parameters.

ParameterPurposeExample
defaultSets a static default valuefield(default=0)
default_factoryProvides a callable that returns the defaultfield(default_factory=list)
initWhether the field appears in __init__field(init=False)
reprWhether the field appears in __repr__field(repr=False)
compareWhether the field is used in equality and orderingfield(compare=False)
hashWhether the field is used in __hash__field(hash=False)
metadataArbitrary data for external usefield(metadata={"unit": "kg"})

Handling Mutable Defaults Correctly

A common mistake is to use a mutable object directly as a default value. For example:

# Wrong: this will raise ValueError @dataclass class Bad: items: list = []

The dataclasses module explicitly rejects this with a ValueError because the same list instance would be shared across all instances. Instead, use default_factory to create a fresh object for each instance.

@dataclass class Good: items: list = field(default_factory=list)

The default_factory is called each time a new instance is created, so every instance gets its own independent list. This applies to any mutable type: dictionaries, sets, custom objects, and even nested structures.

Controlling Field Behavior with field() Options

The init, repr, compare, and hash options give you fine-grained control over how a field participates in the generated methods. For example, a computed field that is derived from other fields might not need to be part of the constructor:

@dataclass class Circle: radius: float area: float = field(init=False, repr=True) def __post_init__(self): self.area = 3.14159 * self.radius ** 2

Here area is excluded from __init__ but still appears in __repr__. The compare option is useful when a field is an internal identifier that should not affect equality. For instance, two orders with the same items but different internal IDs might be considered equal if compare=False is set on the ID field.

The hash option interacts with eq. If eq=True (the default), the generated __hash__ is set to None, making the instance unhashable. Setting hash=True explicitly forces a hash based on all fields that have hash=True. This is useful when you need hashable instances but want to exclude certain fields from the hash.

Using kw_only and Slots for Safer and Faster Fields

Python 3.10 introduced kw_only for dataclasses. When set to True on a field or on the entire dataclass, the field becomes keyword-only in the generated __init__. This prevents positional misassignment and improves readability for classes with many fields.

@dataclass(kw_only=True) class User: id: int name: str email: str

Now User(1, "Alice", "alice@example.com") raises a TypeError; you must use keyword arguments. This is especially valuable when fields have defaults or when the field order is not obvious.

Python 3.10 also added slots=True to dataclasses. By default, dataclass instances use a __dict__ to store attributes, which is flexible but consumes memory. With slots=True, the class uses __slots__, which reduces memory usage and improves attribute access speed. The tradeoff is that you cannot add new attributes dynamically.

@dataclass(slots=True) class Config: host: str port: int ```n Using `slots=True` also prevents accidental typos from creating new attributes, which can catch bugs early. However, inheritance with slots requires careful handling: all base classes must also use slots, or you must manually manage `__slots__`. ## Field Ordering, Inheritance, and Common Pitfalls Field ordering follows the order of declaration in the class body. When a dataclass inherits from another dataclass, the fields from the base class come first, then the derived class's fields. This can cause issues if the base class has fields with defaults and the derived class adds non-default fields, because the generated `__init__` would have non-default arguments after default ones. To avoid this, either give the derived fields defaults or use `kw_only=True`. Another subtlety is that fields without annotations are not considered fields. If you write `x = 0` in a dataclass body, `x` is treated as a class variable, not a field. To make it a field, you must annotate it: `x: int = 0`. This distinction is a frequent source of confusion. Finally, remember that dataclass fields are not automatically validated. The type annotations are not enforced at runtime, and `default_factory` does not validate the returned object. If you need validation, use `__post_init__` to check values and raise exceptions. This keeps validation logic in one place and prevents the same checks from being duplicated across request handlers or other call sites. When you need to store structured data with minimal boilerplate, python dataclass fields provide a clean, maintainable approach. The `field()` function and options like `kw_only` and `slots` give you the control needed for production code without sacrificing readability.
python dataclass fields: Practical Usage and Code Examples | RYUSLOG DEV