PythiloLesson 9 of 15 · progress kept in this browser

From primitives to value objects

Passing money around as a bare float, or as a tuple of amount and currency, spreads its rules everywhere: every caller must remember the unit, check the currency, and format it the same way. This habit has a name, primitive obsession.

A small frozen dataclass gives the idea a name and one home for its rules. It checks itself when created, so an invalid value never exists, and its behaviour lives in methods next to the data.

from dataclasses import dataclass

@dataclass(frozen=True)
class Money:
    cents: int
    currency: str

    def __add__(self, other):
        if other.currency != self.currency:
            message = "mixed currency"
            raise ValueError(message)
        total = self.cents + other.cents
        return Money(total, self.currency)

print(Money(150, "EUR") + Money(75, "EUR"))

Storing whole cents as an int avoids float rounding: 0.1 + 0.2 is not 0.3, but 10 + 20 is 30. Two values with the same fields compare equal, which is exactly how a value should behave.

Your task

Replace the tuples with a frozen dataclass Money(cents, currency). It raises ValueError unless the currency is three capital letters, + adds amounts of the same currency and raises ValueError for different ones, and str() gives text like 12.50 EUR.