Advertisement
🏗️ OOP

Python Magic Methods – Dunder Methods Explained

Magic methods (also called dunder methods — double underscore) are special Python methods that define how objects behave with built-in operations. They let you define what happens when you print an object, add two objects, compare them, use them in a with statement, or iterate over them.

⏱️ 22 min read🎯 Advanced📅 Updated 2026

__str__ and __repr__

__str__: human-readable string (for print). __repr__: developer representation (unambiguous, for debugging).

Python
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

    def __repr__(self):
        return f"Point(x={self.x!r}, y={self.y!r})"

p = Point(3, 4)
print(str(p))   # Point(3, 4)      — uses __str__
print(repr(p))  # Point(x=3, y=4)  — uses __repr__
print(p)        # Point(3, 4)      — print uses __str__
▶ Output
Point(3, 4) Point(x=3, y=4) Point(3, 4)

Comparison Methods

Define how objects are compared with ==, <, >, <=, >= operators.

Python
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    def __eq__(self, other):
        return self.celsius == other.celsius

    def __lt__(self, other):
        return self.celsius < other.celsius

    def __le__(self, other):
        return self.celsius <= other.celsius

t1 = Temperature(25)
t2 = Temperature(30)

print(t1 == t2)   # False
print(t1 < t2)    # True
temps = [Temperature(30), Temperature(10), Temperature(20)]
print([t.celsius for t in sorted(temps)])  # [10, 20, 30]
▶ Output
False True [10, 20, 30]

Arithmetic Methods

Make + - * / work on your objects.

Python
class Money:
    def __init__(self, amount, currency="USD"):
        self.amount = amount
        self.currency = currency

    def __add__(self, other):
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies")
        return Money(self.amount + other.amount, self.currency)

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

wallet = Money(50) + Money(30)
print(wallet)   # USD 80.00
▶ Output
USD 80.00
Advertisement

Container Methods

__len__, __getitem__, __contains__ make custom objects behave like sequences.

Python
class Library:
    def __init__(self):
        self._books = []

    def add(self, book): self._books.append(book)
    def __len__(self): return len(self._books)
    def __getitem__(self, idx): return self._books[idx]
    def __contains__(self, book): return book in self._books

lib = Library()
lib.add("Python Crash Course")
lib.add("Fluent Python")

print(len(lib))                    # 2
print(lib[0])                      # Python Crash Course
print("Fluent Python" in lib)      # True
▶ Output
2 Python Crash Course True

Dunder Methods: Making Your Objects Feel Built-in

"Magic" (dunder) methods let your class hook into Python's syntax. Define __add__ and + works on your objects; define __len__ and len() works. You're not calling them directly — the interpreter does.

class Money:
    def __init__(self, amt): self.amt = amt
    def __repr__(self): return f"Money({self.amt})"    # for developers
    def __str__(self):  return f"${self.amt:.2f}"      # for users
    def __add__(self, other): return Money(self.amt + other.amt)
    def __eq__(self, other):  return self.amt == other.amt

print(Money(5) + Money(3))   # $8.00 — uses __add__ then __str__

Two pairings people get wrong

  • __str__ vs __repr__: str()/print use __str__ (readable); the REPL and containers use __repr__ (unambiguous, ideally reconstructable). Define __repr__ at minimum — it's the fallback for both.
  • __eq__ + __hash__: define __eq__ and Python sets __hash__ to None, making instances unhashable (can't go in sets/dict keys). If two equal objects should hash the same, define __hash__ too.

🏋️ Practical Exercise

Create a Vector class representing a 2D vector:

  1. Implement __init__(self, x, y).
  2. Add __repr__ so the object prints as Vector(2, 3).
  3. Implement __add__ so two vectors can be added with +.
  4. Implement __eq__ so two vectors with the same components compare equal.

🔥 Challenge Exercise

Build a Playlist class that wraps a list of songs and behaves like a container. Implement __len__, __getitem__ (so you can index and iterate it), __contains__ (so in works), and __str__ for a friendly printout. Demonstrate looping over the playlist with a for loop, checking membership, and printing its length.

📋 Summary

  • Magic (dunder) methods have double underscores and let your objects integrate with Python’s built-in syntax.
  • __str__ gives a readable string for users; __repr__ gives an unambiguous string for developers.
  • Comparison methods (__eq__, __lt__, …) enable operators like == and <.
  • Arithmetic methods (__add__, __mul__, …) implement operator overloading.
  • Container methods (__len__, __getitem__, __contains__) make objects behave like lists or dicts.
  • Implementing __repr__ aids debugging because it is what the REPL and containers display.

Interview Questions on Magic Methods

  • What are magic (dunder) methods in Python?
  • What is the difference between __str__ and __repr__?
  • Which magic methods let you use the + and == operators on your objects?
  • How do you make a custom object work with len() and the in operator?
  • What magic methods make an object iterable?
  • What does __call__ do?
  • Why is implementing __repr__ considered good practice?

FAQ

What is the difference between __str__ and __repr__? +

__str__ is for an informal, readable representation shown by print() and str(). __repr__ is for an unambiguous, developer-facing representation shown in the REPL and by containers. If you only define one, define __repr__str() falls back to it.

Are magic methods called directly? +

Rarely. You usually trigger them through syntax: a + b calls a.__add__(b), len(x) calls x.__len__(). Python invokes them for you when you use the corresponding operator or built-in.

What is operator overloading? +

Defining magic methods like __add__ or __mul__ so standard operators work on your own objects. It lets a Vector + Vector or Money * 2 read naturally instead of needing named methods.

How do I make my object iterable? +

Implement __iter__ (returning an iterator) or, more simply, __getitem__ with integer indexes starting at 0. Either lets a for loop walk through your object.