Magic methods
A magic method is a method with double underscores around its name that Python calls for you, so your own objects work with print(), ==, len() and the + operator.
Last updated: 13 Oct, 2026 · Python 3.12
A Ticket prints as a memory address, and two tickets with the same data are not equal. Magic methods are the methods Python already calls on built-in types; defining them on your own class makes it answer the same calls.
dir(object) to list the double-underscore methods, prints an object to see the default <...object at 0x...>, then overrides __str__ so print shows a sentence. It runs each line in a notebook, which shows a value without print(); the run below is his own Person, and the rest of the page uses the course's Ticket, each result wrapped in print() as a .py file needs.class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# __str__ is the friendly line print() shows
def __str__(self):
return f"{self.name},{self.age} years old"
# __repr__ is the exact line the shell and repr() show
def __repr__(self):
return f"Person(name={self.name},age={self.age})"
person = Person("KRish", 34)
print(person)
print(repr(person))KRish,34 years old Person(name=KRish,age=34)
Syntax:
class Name:
def __str__(self): # what print() and str() show
return "..."
def __repr__(self): # what the shell and a list show
return "..."The Ticket we build on
class Ticket:
def __init__(self, id, customer, category):
self.id = id
self.customer = customer
self.category = categoryPrinting with __str__ and __repr__
__str__ is the friendly line print shows. __repr__ is the exact line the shell and a list of objects show, meant to be unambiguous.
def __str__(self):
return f"#{self.id} {self.customer} ({self.category})"
def __repr__(self):
return f"Ticket(id={self.id!r}, customer={self.customer!r}, category={self.category!r})"ticket = Ticket(1, "Asha", "billing")
print(ticket)
print(repr(ticket))
print([ticket])#1 Asha (billing) Ticket(id=1, customer='Asha', category='billing') [Ticket(id=1, customer='Asha', category='billing')]
print(ticket) uses __str__. repr(ticket) and the list use __repr__: a list always shows each item's __repr__, which is why a readable one matters. The !r in the f-string inserts each value's repr, so the strings keep their quotes.
Comparing tickets with __eq__
Without __eq__, == asks whether two names point at the same object, so two tickets with identical data come out unequal. __eq__ says what equal means for your class.
def __eq__(self, other):
return (self.id, self.customer, self.category) == (other.id, other.customer, other.category)a = Ticket(1, "Asha", "billing")
b = Ticket(1, "Asha", "billing")
print(a == b)
print(a is b)True False
a == b is now True because the data matches, while a is b stays False because they are still two separate objects. Before __eq__ was added, a == b was False too.
Giving a collection a length with __len__
len(x) calls x.__len__(). A class that holds a list of messages, the chat shape from Lists of dictionaries, can report its own length:
class Conversation:
def __init__(self):
self.messages = []
def add(self, role, text):
self.messages.append({"role": role, "text": text})
def __len__(self):
return len(self.messages)chat = Conversation()
chat.add("user", "I was charged twice")
chat.add("assistant", "Let me look into that")
print(len(chat))2
len(chat) returns the number of messages, so the object behaves like a built-in container without exposing its list.
Adding objects with __add__
The + operator calls __add__. A small vector class shows it clearly: v1 + v2 builds a new vector from the two.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# + on two Vectors builds and returns a new Vector
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2)
print(v1 + v2 == Vector(6, 8))Vector(6, 8) True
v1 + v2 runs __add__, which returns a new Vector, and __repr__ makes it print as Vector(6, 8). Because Vector also defines __eq__, the new vector compares equal to Vector(6, 8). The same idea gives a class -, * or / through __sub__, __mul__ and __truediv__.
Default object vs magic methods
| Call | Default object | With the magic method |
|---|---|---|
print(t) | A memory address | __str__, a readable line |
t1 == t2 | Same object only | __eq__, equal by value |
len(t) | TypeError | __len__, a count you choose |
a + b | TypeError | __add__, a value you build |
Where magic methods show up in AI code
- Framework objects print a readable
__repr__, so a message or a result shows its fields in the shell. - Tests compare result objects with
==, which needs__eq__to compare by value. - A wrapper around a list of messages or documents answers
len()through__len__.
__eq__ makes a class unhashable, so it can no longer be a set member or a dict key, unless you also define __hash__. Dataclasses handle this for you, which is one reason dataclasses exists.Related
- Previous: Methods
- Next: Class and static methods
- Reference: Special method names in the Python docs
- Add
__lt__so tickets sort, then callsortedon a list of three. - Give
Conversationa__getitem__sochat[0]returns the first message. - Define
__eq__onTicketand try putting two tickets in aset; read the error, then add__hash__.
Every expert started right here.