DEV Community

Cover image for Mastering Python Dunder Methods: Building Production-Grade Value Objects

Mastering Python Dunder Methods: Building Production-Grade Value Objects

M_ost Python developers know how to write a basic class. They define an __init_ method, maybe a few custom functions, and call it a day.

But in production-grade backend engineering, especially when building complex systems with FastAPI and AI integrations, basic classes aren't enough. You need Value Objects—objects whose equality, ordering, and hashing are determined entirely by their data, not by where they live in memory.

To do this, you have to master Dunder Methods (Double Underscore methods).

Here is how I build robust, Pythonic classes using the data model.

  1. Representation: repr vs str

You need two ways for your object to represent itself as a string.

· repr is for developers. It should ideally be an unambiguous string that could recreate the object.
· str is for end-users. It should be readable and clean.

class Money:
    def __init__(self, amount: float, currency: str):
        self.amount = amount
        self.currency = currency

    def __repr__(self) -> str:
        return f"Money(amount={self.amount}, currency='{self.currency}')"

    def __str__(self) -> str:
        return f"{self.amount} {self.currency}"
Enter fullscreen mode Exit fullscreen mode

2. Equality and Hashing: eq and hash

If two objects hold the same data (e.g., $10 USD), they should be considered equal. If you implement eq, you must also implement hash so the object can be used in sets or as dictionary keys.

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Money):
            return NotImplemented
        return self.amount == other.amount and self.currency == other.currency

    def __hash__(self) -> int:
        return hash((self.amount, self.currency))
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Returning NotImplemented instead of False when comparing to a non-Money object allows Python's comparison machinery to try the other object's eq method.

3. Ordering: lt and @total_ordering

To sort objects, Python uses lt (less than). But you don't need to write gt, le, and ge manually. Just use the functools module.

from functools import total_ordering

@total_ordering
class Money:
    # ... init and eq ...

    def __lt__(self, other: object) -> bool:
        if not isinstance(other, Money):
            return NotImplemented
        if self.currency != other.currency:
            raise TypeError("Cannot order amounts in different currencies.")
        return self.amount < other.amount
Enter fullscreen mode Exit fullscreen mode

The @total_ordering decorator looks at your eq and lt methods and automatically generates >, <=, and >= for you.

**
 **
When you build AI pipelines or API backends, data consistency is everything. By implementing dunder methods correctly, you ensure that your custom objects can be sorted, deduplicated, and used as dictionary keys without silent bugs creeping into your database.

Mastering the Python Data Model is the difference between writing scripts and engineering systems.

Top comments (0)