Python Magic Methods
Learn how Python objects interact with built-in functions, operators, comparisons, indexing, iteration, and more by using special methods known as magic methods.
Learning Objectives
By the end of this lesson, you will understand how Python's special methods allow your custom classes to behave naturally with Python's built-in functions and operators.
- Understand what magic methods are.
- Understand the meaning of "dunder methods".
- Customize how objects are displayed.
- Make objects work with
len(). - Control object comparisons.
- Overload operators such as
+. - Support membership and indexing.
- Understand the relationship between magic methods and iteration.
- Make objects callable.
- Understand the basics of context-manager methods.
What Are Magic Methods?
Magic methods are special methods in Python that allow objects to interact with built-in Python operations.
They are usually surrounded by double underscores:
__method__.
You normally do not call these methods directly. Instead, Python calls them automatically when you perform certain operations.
class Student:
def __init__(self, name):
self.name = name
student = Student("Ali")
When you create the object:
student = Student("Ali")
Python automatically calls:
student.__init__("Ali")
Why Are They Called Dunder Methods?
The word dunder is short for double underscore.
For example:
__init__
__str__
__repr__
__len__
__eq__
Each method begins and ends with two underscores.
The term "magic method" and "dunder method" are commonly used to describe these special methods.
__init__() Revisited
You have already seen __init__() in previous
object-oriented programming lessons.
It is automatically executed when a new object is created.
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
student = Student("Ali", 21)
print(student.name)
print(student.age)
Ali
21
The constructor prepares the object's initial state.
__str__()
The __str__() method controls the human-readable
representation of an object.
It is especially useful when using print().
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} ({self.age})"
student = Student("Ali", 21)
print(student)
Ali (21)
Without __str__(), printing a custom object
usually produces a less useful representation containing
the class name and memory-related information.
__repr__()
__repr__() provides a developer-oriented
representation of an object.
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Student(name={self.name!r}, age={self.age!r})"
student = Student("Ali", 21)
print(repr(student))
Student(name='Ali', age=21)
__str__() is generally intended to be
user-friendly, while __repr__() is
generally intended to help developers understand
or debug an object.
__len__()
The __len__() method allows your custom object
to work with Python's built-in len() function.
class Team:
def __init__(self, members):
self.members = members
def __len__(self):
return len(self.members)
team = Team(["Ali", "Sara", "Ahmed"])
print(len(team))
3
Python internally uses the object's __len__()
method when you call len(team).
__eq__()
The __eq__() method controls what happens when
two objects are compared using ==.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __eq__(self, other):
return (
self.name == other.name
and self.price == other.price
)
product1 = Product("Laptop", 1000)
product2 = Product("Laptop", 1000)
print(product1 == product2)
True
__eq__()
implementation should usually verify that the other
object is of a compatible type before accessing its
attributes.
Comparison Magic Methods
Python provides special methods for ordering objects.
| Operator | Magic Method |
|---|---|
< |
__lt__() |
<= |
__le__() |
> |
__gt__() |
>= |
__ge__() |
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __lt__(self, other):
return self.price < other.price
phone = Product("Phone", 500)
laptop = Product("Laptop", 1000)
print(phone < laptop)
True
This can be especially useful when sorting custom objects.
__add__()
The __add__() method controls the behavior of
the + operator for your custom objects.
class Money:
def __init__(self, amount):
self.amount = amount
def __add__(self, other):
return Money(self.amount + other.amount)
money1 = Money(500)
money2 = Money(300)
total = money1 + money2
print(total.amount)
800
Other Arithmetic Methods
Python provides magic methods for many arithmetic operators.
| Operator | Magic Method |
|---|---|
+ |
__add__() |
- |
__sub__() |
* |
__mul__() |
/ |
__truediv__() |
// |
__floordiv__() |
% |
__mod__() |
** |
__pow__() |
You should only implement these methods when the operation makes natural sense for your object.
__contains__()
The __contains__() method controls membership
testing with the in operator.
class Course:
def __init__(self, students):
self.students = students
def __contains__(self, name):
return name in self.students
course = Course(["Ali", "Sara", "Ahmed"])
print("Ali" in course)
print("John" in course)
True
False
__getitem__()
__getitem__() allows an object to support
square-bracket indexing.
class Playlist:
def __init__(self, songs):
self.songs = songs
def __getitem__(self, index):
return self.songs[index]
playlist = Playlist([
"Song A",
"Song B",
"Song C"
])
print(playlist[0])
print(playlist[2])
Song A
Song C
Python calls __getitem__() when you use:
playlist[0]
__setitem__()
The __setitem__() method controls assignment
through square brackets.
class Playlist:
def __init__(self, songs):
self.songs = songs
def __getitem__(self, index):
return self.songs[index]
def __setitem__(self, index, value):
self.songs[index] = value
playlist = Playlist([
"Song A",
"Song B"
])
playlist[1] = "New Song"
print(playlist[1])
New Song
__iter__() and __next__()
Python also uses magic methods to support iteration.
Two important methods are:
__iter__()— returns an iterator.__next__()— returns the next item.
Iterators and the deeper behavior of
__iter__() and __next__()
are covered in detail in the next lesson.
For now, remember that Python's for loops rely
on the iteration protocol.
__call__()
The __call__() method allows an object to be
called like a function.
class Greeter:
def __init__(self, name):
self.name = name
def __call__(self):
print(f"Hello, {self.name}!")
greeter = Greeter("Ali")
greeter()
Hello, Ali!
Normally an object is used like:
greeter
But with __call__(), this becomes possible:
greeter()
You can check whether an object is callable using:
callable(greeter)
__enter__() and __exit__()
Magic methods can also be used to build context managers.
Context managers work with the with statement.
class Demo:
def __enter__(self):
print("Entering")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting")
Python calls __enter__() when entering the
with block and __exit__() when
leaving it.
Context managers are particularly useful for resources such as files, database connections, locks, and network resources.
Common Magic Methods at a Glance
| Magic Method | Purpose |
|---|---|
__init__() |
Initialize an object |
__str__() |
Human-readable representation |
__repr__() |
Developer-oriented representation |
__len__() |
Support len() |
__eq__() |
Support == |
__lt__() |
Support < |
__add__() |
Support + |
__contains__() |
Support in |
__getitem__() |
Support indexing |
__setitem__() |
Support indexed assignment |
__call__() |
Make an object callable |
__iter__() |
Support iteration |
__next__() |
Return the next item from an iterator |
Real-World Examples
Magic methods become particularly useful when building classes that should behave naturally like Python's built-in objects.
Ecommerce Products
A product object might support:
__str__()for displaying product information.__eq__()for comparing products.__lt__()for price sorting.__len__()for inventory quantity.
Shopping Carts
A shopping cart could implement __len__()
to return the number of products.
Financial Applications
A custom money class can implement arithmetic operations such as addition and subtraction.
Data Structures
Custom collections can implement indexing, membership, iteration, and length behavior.
Common Mistakes
1. Using magic methods unnecessarily
Do not overload operators just because you can. The behavior should make logical sense.
2. Returning the wrong type
Methods such as __str__() must return
a string.
3. Forgetting self
Instance magic methods normally receive
self as their first parameter.
4. Making comparisons unsafe
When implementing comparison methods, consider what happens if the other object is not compatible.
5. Making surprising operators
If + does something unexpected,
your class becomes difficult to understand.
Practice Exercises
Exercise 1 — Student
Create a Student class with
name and age.
Implement __str__().
Exercise 2 — Team
Create a Team class containing a list of
members. Implement __len__().
Exercise 3 — Product Comparison
Create two products and implement
__eq__().
Exercise 4 — Price Sorting
Implement __lt__() so products can be
compared by price.
Exercise 5 — Playlist
Implement __getitem__() so users can
access songs using square brackets.
Exercise 6 — Callable Object
Create a class with __call__() and make
an object behave like a function.
Mini Challenge — Custom Product Class
Build a professional Product class that uses
several magic methods.
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def __str__(self):
return f"{self.name} - ${self.price}"
def __repr__(self):
return (
f"Product("
f"name={self.name!r}, "
f"price={self.price!r}, "
f"stock={self.stock!r})"
)
def __eq__(self, other):
return (
self.name == other.name
and self.price == other.price
)
def __lt__(self, other):
return self.price < other.price
def __len__(self):
return self.stock
product1 = Product("Laptop", 1000, 5)
product2 = Product("Phone", 600, 10)
print(product1)
print(repr(product1))
print(product1 == product2)
print(product1 < product2)
print(len(product1))
Your Tasks
- Create at least three products.
- Test
__str__(). - Test
__repr__(). - Compare two products with
==. - Compare products by price.
- Use
len()to check stock. - Put your products into a list and sort them.
- Add another useful magic method if it makes sense.
Do not add magic methods simply to increase the number of methods. Ask whether the behavior is intuitive for someone using your class.
Knowledge Check
Test your understanding of Python magic methods.
1. What are magic methods?
2. What does "dunder" mean?
3. Which method controls print-friendly object output?
4. Which method supports len()?
5. Which method controls ==?
6. Which method controls the + operator?
7. Which method allows custom indexing?
8. What does __call__() allow?
9. Which method is associated with iteration?
10. Should every class implement many magic methods?
Lesson Summary
- Magic methods are special methods used by Python to define object behavior.
- They are commonly called dunder methods because their names use double underscores.
-
__init__()initializes objects. -
__str__()provides a readable representation. -
__repr__()provides a developer-oriented representation. -
__len__()allows objects to work withlen(). -
Comparison methods control operators such as
==and<. - Arithmetic magic methods allow operator overloading.
-
__contains__()supports membership testing. -
__getitem__()and__setitem__()support indexing. -
__call__()allows objects to behave like functions. -
__iter__()and__next__()are important parts of Python's iteration protocol. - Magic methods should be used thoughtfully so that custom objects behave naturally and predictably.
Great Work!
You now understand how Python's magic methods connect custom classes with Python's built-in behavior.