Python for AIPython 3.12 · Pydantic 2.12
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
43 small wins to finish your pathNext lesson →

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() and overriding __str__ · from Advance Python Series-Magic Methods In Classes · 2:22 to 6:43
The video runs 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.
ExampleFrom the video, run here
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))

Syntax:

python
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

python
class Ticket:
    def __init__(self, id, customer, category):
        self.id = id
        self.customer = customer
        self.category = category

Printing 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.

python
    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})"
Example
ticket = Ticket(1, "Asha", "billing")
print(ticket)
print(repr(ticket))
print([ticket])

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.

python
    def __eq__(self, other):
        return (self.id, self.customer, self.category) == (other.id, other.customer, other.category)
Example
a = Ticket(1, "Asha", "billing")
b = Ticket(1, "Asha", "billing")
print(a == b)
print(a is b)

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:

python
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)
Example
chat = Conversation()
chat.add("user", "I was charged twice")
chat.add("assistant", "Let me look into that")
print(len(chat))

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.

Example
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))

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

CallDefault objectWith the magic method
print(t)A memory address__str__, a readable line
t1 == t2Same object only__eq__, equal by value
len(t)TypeError__len__, a count you choose
a + bTypeError__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__.
Watch out. Defining __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.
Try it yourself
  • Add __lt__ so tickets sort, then call sorted on a list of three.
  • Give Conversation a __getitem__ so chat[0] returns the first message.
  • Define __eq__ on Ticket and try putting two tickets in a set; read the error, then add __hash__.
PreviousMethods

Every expert started right here.