Python 3.14 ·
✓ verified by execution on 2026-07-26
When you use Python’s built-in syntax—like adding two numbers with +, checking length with len(), or printing an object—Python handles the operation by looking for specific methods behind the scenes. These special methods are often called dunder methods because their names begin and end with “double underscores”, such as __init__ or __str__.
By defining dunder methods in your own classes, you can seamlessly integrate your custom objects with Python’s built-in functions and operators. This is what gives Python its distinctive, readable flair.
String Representations
The most common dunder methods you will use dictate how an object is converted into a string. Python provides two distinct string representations: the official representation (__repr__) and the informal representation (__str__).
The Official Representation: __repr__
The __repr__ method computes the “official” string representation of an object. Its goal is to be unambiguous and helpful for developers who are debugging. If at all possible, the string returned by __repr__ should look like a valid Python expression that could be used to recreate the object.
python
class Point: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return f'Point({self.x}, {self.y})'p = Point(1, 2)print(repr(p))
Output
Point(1, 2)
Your output
The Informal Representation: __str__
While __repr__ is for developers, the __str__ method computes the “informal” or nicely printable string representation of an object. This is what users should see. It is automatically called when you pass your object to print() or str().
If you define __repr__ but omit __str__, Python will fall back to using your __repr__ method for printing. However, if you only define __str__, collections like lists will still use the default memory-address representation when printing their elements. It is generally a best practice to always define __repr__.
Customizing Equality
By default, if you use the == operator on two custom objects, Python simply checks if they are the exact same object in memory (their identity). If you want to compare their actual values, you must override the __eq__ method.
The __eq__ method is part of Python’s “rich comparison” toolkit. When you evaluate x == y, Python secretly calls x.__eq__(y).
python
class Item: def __init__(self, name, price): self.name = name self.price = price def __eq__(self, other): if not isinstance(other, Item): return False return self.name == other.name and self.price == other.pricei1 = Item('Apple', 1.0)i2 = Item('Apple', 1.0)print(i1 == i2)
Output
True
Your output
Notice that we first check if the other object is actually an instance of Item. Failing to check isinstance() can lead to an AttributeError if your object is accidentally compared to a totally different type, like an integer or a string.
Emulating Containers
If you want your object to behave like a collection (such as a list or a dictionary), you can define methods that Python expects containers to have. For example, the __len__ method allows custom objects to be passed to the built-in len() function.
python
class Team: def __init__(self, members): self.members = members def __len__(self): return len(self.members)t = Team(['Alice', 'Bob', 'Charlie'])print(len(t))
Output
3
Your output
If __len__ returns a negative number or a non-integer, Python will raise an error.
Operator Overloading
Operator overloading allows your custom objects to use standard mathematical symbols like +, -, or *. You implement this by defining arithmetic dunder methods.
For instance, to evaluate the expression x + y, Python invokes the __add__ method. This allows you to construct intuitive interfaces for mathematical types like Vectors.
You can also combine your object with standard Python data types. If we want to multiply a vector by an integer scalar, we can implement the __mul__ method (*).
Dunder methods are the secret sauce that makes Python feel cohesive. By hooking into the data model, your custom classes can participate in Python’s ecosystem exactly like the built-in types do.
Check yourself
Why should you define `__repr__` even if you already have `__str__` defined?
Reveal answer
Collections like lists will always use `__repr__` when displaying their elements, ignoring `__str__`. — While `print()` and `str()` fall back to `__repr__` if `__str__` is missing, the reverse is not true. Collections like lists always call `__repr__` on their elements, making `__repr__` essential for debugging and proper display.
What is the default behavior when comparing two custom objects with `==` if `__eq__` is not defined?
Reveal answer
It compares the objects' memory identities, exactly like the `is` operator. — By default, custom objects inherit `__eq__` from the base `object` class, which simply compares memory addresses (identity). You must explicitly override `__eq__` to compare them by value.
How are dunder methods meant to be executed?
Reveal answer
Indirectly, by using built-in operators (like `+`) and functions (like `len()`). — While you can call them directly, dunder methods are intended to hook into Python's built-in syntax. For instance, `len(obj)` automatically calls `obj.__len__()`, and `a + b` automatically calls `a.__add__(b)`.
Does defining `__eq__` automatically create a working `__ne__` (not equal) method?
Reveal answer
Yes, in Python 3, `__ne__` automatically falls back to the inverse of `__eq__`. — In Python 3, `__ne__` does default to the inverse of `__eq__`. However, for other comparison operators like `<` or `>`, you must define them manually or use the `functools.total_ordering` decorator.
Challenges
Challenge 1 +50 XP
Fix the bug! The `Product` class overrides `__eq__` so that two products with the same name are considered equal. However, checking `product == 'Apple'` crashes because a string doesn't have a `.name` attribute. Use `isinstance()` to fix `__eq__` so it returns `False` if `other` is not a `Product`.
python
Test 1 — expects "False\n"
Need a hint? (−25% XP)
Add `if not isinstance(other, Product): return False` at the beginning of the `__eq__` method.
Show solution (0 XP)
class Product: def __init__(self, name): self.name = name def __eq__(self, other): if not isinstance(other, Product): return False return self.name == other.namep = Product('Apple')print(p == 'Apple')
Challenge 2 +100 XP
Create a `Wallet` class that holds a number of `coins`. Implement the `__add__` dunder method so that adding two wallets returns a new `Wallet` with the combined total of coins. Implement `__str__` to print `Wallet with X coins`.
python
Test 1 — expects "Wallet with 25 coins\n"
Need a hint? (−25% XP)
Define `def __add__(self, other): return Wallet(self.coins + other.coins)` and `def __str__(self): return f'Wallet with {self.coins} coins'`.