Python Tutorial / Create Data-Centered Classes with dataclass

dataclass generates methods such as initialization, representation and equality from field declarations. It simplifies record-like classes but does not automatically validate type annotations or business rules.

Reduce repeated record boilerplate

The decorator discovers fields from class annotations and normally generates __init__, __repr__ and __eq__. That is convenient for small records, while classes centered on complex behavior may need a different design.

Use field(default_factory=list) for a new mutable list per instance. The factory is called to create each default rather than making every record share one list.

For the relevant APIs and platform requirements, see Python: dataclasses.

Field sharing and JSON conversion build on the rules demonstrated in Distinguish Instance Attributes from Class Attributes.

Run separate tags and field-based equality

Save the complete code below as data_records.py in a separate practice folder, using UTF-8. Run python data_records.py in a terminal with your project virtual environment selected. Outside a virtual environment on macOS or Ubuntu, use python3 when that is your interpreter command.

from dataclasses import dataclass, field

@dataclass
class Expense:
    name: str
    amount: int
    tags: list[str] = field(default_factory=list)

    def __post_init__(self):
        if type(self.amount) is not int or self.amount < 0:
            raise ValueError("invalid amount")

a = Expense("lunch", 8000)
b = Expense("bus", 2500)
a.tags.append("work")
print(a)
print(b.tags)
print(a == Expense("lunch", 8000, ["work"]))

Expected output:

Expense(name='lunch', amount=8000, tags=['work'])
[]
True

Understand validation and frozen boundaries

__post_init__ runs after the generated initializer. The example validates the initial amount but does not prevent a later a.amount = -1 assignment. Continuing validation may require a property or a different immutable-state design.

frozen=True restricts ordinary reassignment of fields; it does not prevent append on a list stored inside a field. A genuinely immutable record requires appropriate nested types, such as tuples, and a clear contract.

dataclass generates methods such as __init__, __repr__ and __eq__ from field declarations, but does not automatically enforce business validation. It reduces initialization and representation boilerplate. Use field(default_factory=list) for a separate list per instance; frozen=True does not make a referenced list immutable.

Define equality and serialization for the application

Equal fields can make two records compare equal, but business identity may require a separate transaction identifier. Two expenses with the same name and amount are not necessarily the same event.

dataclasses.asdict helps construct dictionary data but does not automatically make dates or Decimal values JSON serializable. Verify independent tags and rejected initial amounts separately, then test the conversion and storage policy. The JSON lesson explains why converting an object and validating its data are different steps.

Try creating a record with a negative amount, then restore the valid input. Compare generated equality with the identity rule your eventual expense application needs before using it for duplicate detection.