How to Create an Immutable Money Class in Python with dataclasses

Define a frozen dataclass Money that holds an amount and currency, enforces non-negative amounts, and supports safe addition across matching currencies.

Easy Python 3.9+ Aug 9, 2026 OOP & classes 16 views 0 copies

Python code

30 lines
Python 3.9+
from dataclasses import dataclass


@dataclass(frozen=True)
class Money:
    amount: float
    currency: str = "USD"

    def __post_init__(self) -> None:
        if self.amount < 0:
            raise ValueError("amount must be non-negative")

    def add(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError("currencies must match")
        return Money(self.amount + other.amount, self.currency)

    def __str__(self) -> str:
        return f"{self.amount:.2f} {self.currency}"


if __name__ == "__main__":
    price = Money(19.99)
    tax = Money(1.60)
    total = price.add(tax)
    print(total)
    try:
        bad = Money(-5)
    except ValueError as e:
        print(f"Error: {e}")

Output

stdout
21.59 USD
Error: amount must be non-negative

How it works

The @dataclass(frozen=True) decorator makes instances immutable, preventing accidental mutation after creation. __post_init__ runs validation right after initialization, raising a clear error for negative amounts. The add method checks currency compatibility and returns a new Money object, preserving immutability. The __str__ method formats the amount to two decimal places for clean display.

Common mistakes

  • Forgetting to check for negative amounts in __post_init__
  • Attempting to modify a frozen dataclass attribute, which raises FrozenInstanceError
  • Allowing addition of different currencies without validation

Variations

  1. Use a NamedTuple instead of a dataclass for a simpler immutable representation
  2. Add a `to_dict` method or make Money a TypedDict for interfacing with JSON

Real-world use cases

  • Representing monetary values in financial applications to ensure no accidental changes to calculated totals.
  • Passing currency-specific prices through microservices without risk of mutation during processing.
  • Using immutable money objects as keys in dictionaries or elements in sets, since they are hashable.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from OOP & classes

Related tutorials and quizzes for this topic.