Magic methods, also called dunder methods because of their double underscores, are special method names that Python calls automatically for built-in operations. When you write print(obj), Python calls obj.__str__(); a == b calls a.__eq__(b); len(obj) calls obj.__len__(); a + b calls a.__add__(b).
class Station:
def __init__(self, code):
self.code = code
def __str__(self):
return f"Station {self.code}"
print(Station("RIV")) # Station RIVDefining these methods lets your own classes behave like Python's built-in types. This lesson covers text representations, equality and ordering, and the other protocols you'll use most, using weather monitoring stations as the example. You should already be comfortable with classes and objects.
Special protocols
Each magic method connects your class to a specific piece of syntax or built-in function:
| You write | Python calls |
|---|---|
Station("RIV") | __init__ (after __new__) |
str(s), print(s), f-strings | __str__ |
repr(s), the shell, inside lists | __repr__ |
a == b, a < b | __eq__, __lt__ |
len(s), if s: | __len__, __bool__ |
a + b, a * 3 | __add__, __mul__ |
s[key], x in s | __getitem__, __contains__ |
for x in s | __iter__ |
with s: | __enter__, __exit__ |
You don't call these methods directly; you use the normal syntax and Python looks them up on the class. Only the names Python defines have special meaning. Inventing __colour__ creates an ordinary method with an awkward name, and PEP 8 reserves the double-underscore-both-sides style for Python's own names.
User text
__str__ returns the readable text shown to users by print(), str() and f-strings. It must return a string:
class Station:
def __init__(self, code, name):
self.code, self.name = code, name
def __str__(self):
return f"{self.name} ({self.code})"
riverside = Station("RIV", "Riverside")
print(riverside) # Riverside (RIV)
print(f"Reading from {riverside}")A common mistake is to print() inside __str__ instead of returning. That prints the text once, then Python complains TypeError: __str__ returned non-string (type NoneType). If a class defines no __str__, Python falls back to __repr__.
Diagnostic text
__repr__ returns an unambiguous description for developers. It's what the interactive shell shows, what appears when the object is inside a list or dictionary, and what debuggers and logs display. The convention is to make it look like the code that would recreate the object:
def __repr__(self):
return f"Station(code={self.code!r}, name={self.name!r})"
print([riverside]) # [Station(code='RIV', name='Riverside')]The !r conversion adds quotes around strings, so 'RIV' can't be mistaken for a variable name or a number. If you write only one of the two methods, write __repr__, since str() falls back to it but not the other way round.
Keep secrets out of representations. A repr that includes an API key or password will end up in log files and error reports; show a masked value or leave the field out.
Comparisons
By default, == on objects compares identity: two separate Station("RIV") objects are not equal. Define __eq__ to compare by value:
def __eq__(self, other):
if not isinstance(other, Station):
return NotImplemented
return self.code == other.codeReturning NotImplemented (a special constant, not an exception) tells Python "I don't know how to compare with that type", so it can try the other object's method and finally fall back to False for ==. Don't raise an error or return False directly.
Defining __eq__ sets __hash__ to None, making instances unusable in sets and as dictionary keys, because equal objects must have equal hashes. If your objects are effectively immutable, add def __hash__(self): return hash(self.code). For ordering (<, >, sorted()), define __lt__, and use functools.total_ordering to generate the rest from __eq__ and __lt__. A @dataclass can generate all of this for you.
Independent practice and a worked check
Task: add friendly and diagnostic station representations and compare equal station values.
class Station:
def __init__(self, code, name):
self.code, self.name = code, name
def __str__(self):
return f"{self.name} station"
def __repr__(self):
return f"Station({self.code!r}, {self.name!r})"
def __eq__(self, other):
if not isinstance(other, Station):
return NotImplemented
return self.code == other.code
def __hash__(self):
return hash(self.code)
a, b = Station("RIV", "Riverside"), Station("RIV", "River Side")
print(a) # Riverside station
print(repr(b)) # Station('RIV', 'River Side')
print(a == b, a is b) # True False
print(len({a, b})) # 1Equal codes make the two objects equal and give them the same hash, so the set keeps only one. For the worked check, the method intended for diagnostic representation is __repr__.
Reference and next practice
The data model chapter of the language reference lists every special method name. Continue with class methods and properties and type hints and dataclasses. Practise with the Python exercises and Python quiz.
Python Magic Methods Summary
- Special protocols: Defined special methods connect classes with Python operations.
- User text: __str__ returns a friendly string representation.
- Diagnostic text: __repr__ helps developers inspect the important state of an object.
- Comparisons: __eq__ and ordering methods define comparisons for supported types.
Python Magic Methods Examples
Each example shows the code, the output it produces and what each part does.
Python Magic Methods in a small program
class Station:
def __init__(self, code):
self.code = code
def __str__(self):
return f"Station {self.code}"
def __repr__(self):
return f"Station({self.code!r})"
station = Station("RIV")
print(str(station))
print(repr(station))Station RIV
Station('RIV')Line-by-line explanation
- Lines 4 to 5
__str__gives the friendly text thatstr()andprint()use. - Lines 6 to 7
__repr__gives the developer view;!rputs quotes round the code. - Lines 10 to 11Calling
str()andrepr()explicitly shows both.
Equality, ordering, length and addition
from functools import total_ordering
@total_ordering
class Rainfall:
def __init__(self, station, readings):
self.station = station
self.readings = list(readings)
def total(self):
return sum(self.readings)
def __repr__(self):
return f"Rainfall({self.station!r}, {self.readings})"
def __eq__(self, other):
if not isinstance(other, Rainfall):
return NotImplemented
return self.total() == other.total()
def __lt__(self, other):
if not isinstance(other, Rainfall):
return NotImplemented
return self.total() < other.total()
def __len__(self):
return len(self.readings)
def __add__(self, other):
if not isinstance(other, Rainfall):
return NotImplemented
return Rainfall(f"{self.station}+{other.station}", self.readings + other.readings)
a = Rainfall("RIV", [4, 7, 1])
b = Rainfall("HIL", [6, 6])
print(a == b, a > b, len(a))
print(a + b)
print(sorted([a, b, Rainfall("COA", [2])]))
print(a == "RIV")True False 3
Rainfall('RIV+HIL', [4, 7, 1, 6, 6])
[Rainfall('COA', [2]), Rainfall('RIV', [4, 7, 1]), Rainfall('HIL', [6, 6])]
FalseLine-by-line explanation
- Line 3
total_orderingfills in>,<=and>=from__eq__and__lt__. - Lines 15 to 18Both stations total 12 mm, so they compare equal.
- Lines 28 to 31
__add__returns a new object rather than changing either operand. - Line 38Comparing with a string returns
NotImplemented, which Python turns intoFalse.
Common Python Magic Methods Mistakes
Printing instead of returning in __str__
class Station:
def __str__(self):
print(f"Station {self.code}")class Station:
def __str__(self):
return f"Station {self.code}"__str__ and __repr__ must return a string. Printing inside them leads to TypeError: __str__ returned non-string.
Defining __eq__ and then putting objects in a set
class Station:
def __init__(self, code):
self.code = code
def __eq__(self, other):
return self.code == other.code
{Station("RIV")} # TypeError: unhashable type: 'Station'class Station:
def __init__(self, code):
self.code = code
def __eq__(self, other):
return isinstance(other, Station) and self.code == other.code
def __hash__(self):
return hash(self.code)Python Magic Methods in Real Projects
Magic methods in libraries you use
Much of Python's ecosystem is built on these protocols. pandas DataFrames define __getitem__ so df["price"] selects a column, and __add__ so whole columns can be added. pathlib.Path defines __truediv__ so Path("data") / "file.csv" builds a path. SQLAlchemy defines __eq__ on columns so User.name == "Ana" builds a SQL condition instead of returning a boolean.
In your own code, a good __repr__ pays for itself the first time you debug: log lines and test failures show Station(code='RIV') instead of <Station object at 0x7f...>. For classes that are mainly data, a dataclass generates __init__, __repr__ and __eq__ automatically.
Python Magic Methods FAQ
What are dunder methods in Python?
"Dunder" is short for "double underscore". Dunder methods, such as __init__, __str__ and __len__, are special methods Python calls automatically when you use built-in syntax or functions on an object. They are also called magic methods or special methods.
What is the difference between __str__ and __repr__?
__str__ returns readable text for end users and is used by print() and str(). __repr__ returns precise text for developers and is used by repr(), the interactive shell and containers. When __str__ is missing, Python uses __repr__ for both.
Why return NotImplemented instead of False in __eq__?
NotImplemented lets Python try the other object's __eq__ before deciding. If neither side knows how to compare, == falls back to an identity check and gives False. Returning False yourself would block the other class's comparison logic.
Is __init__ a magic method?
Yes. Python calls __init__ automatically after creating a new instance, to set up its attributes. The object itself is created by __new__, which you rarely need to override.
How do I make my class work with len()?
Define __len__(self) and return a non-negative integer. Python also uses __len__ for truth testing when __bool__ isn't defined, so an object with length 0 counts as false in an if statement.
Python Magic Methods Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Which method is intended for diagnostic representation?
Which review concern is valid for Python magic methods?
Python Magic Methods Quiz
2 questions to check what you learned.
Which method is intended for diagnostic representation?
- __str__ only
- __repr__
- Python ignores the distinction
Which statement correctly describes diagnostic text?
- __repr__ helps developers inspect the important state of an object.
- Only the example output matters, not the rule
- Every operation has the same behavior