Home Lessons About
Home / Lessons / Magic Methods
Lesson 49 Object-Oriented Programming

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.

50–60 minutes Intermediate OOP
Reading progress 0%
01

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.

You will learn how to:
  • 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.
02

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.

Example
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")
Magic methods give your classes a way to participate in Python's normal language behavior.
03

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.

Dunder = Double Underscore

The term "magic method" and "dunder method" are commonly used to describe these special methods.

04

__init__() Revisited

You have already seen __init__() in previous object-oriented programming lessons.

It is automatically executed when a new object is created.

__init__()
class Student:

    def __init__(self, name, age):
        self.name = name
        self.age = age


student = Student("Ali", 21)

print(student.name)
print(student.age)
Output
Ali
21

The constructor prepares the object's initial state.

05

__str__()

The __str__() method controls the human-readable representation of an object.

It is especially useful when using print().

Readable object output
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)
Output
Ali (21)

Without __str__(), printing a custom object usually produces a less useful representation containing the class name and memory-related information.

06

__repr__()

__repr__() provides a developer-oriented representation of an object.

__repr__()
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))
Output
Student(name='Ali', age=21)
str vs repr

__str__() is generally intended to be user-friendly, while __repr__() is generally intended to help developers understand or debug an object.

07

__len__()

The __len__() method allows your custom object to work with Python's built-in len() function.

Custom length
class Team:

    def __init__(self, members):
        self.members = members

    def __len__(self):
        return len(self.members)


team = Team(["Ali", "Sara", "Ahmed"])

print(len(team))
Output
3

Python internally uses the object's __len__() method when you call len(team).

08

__eq__()

The __eq__() method controls what happens when two objects are compared using ==.

Comparing custom objects
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)
Output
True
In production code, a robust __eq__() implementation should usually verify that the other object is of a compatible type before accessing its attributes.
09

Comparison Magic Methods

Python provides special methods for ordering objects.

Operator Magic Method
< __lt__()
<= __le__()
> __gt__()
>= __ge__()
Example
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)
Output
True

This can be especially useful when sorting custom objects.

10

__add__()

The __add__() method controls the behavior of the + operator for your custom objects.

Operator overloading
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)
Output
800
This is called operator overloading: you define how an operator behaves for your own class.
11

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.

12

__contains__()

The __contains__() method controls membership testing with the in operator.

Membership testing
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)
Output
True
False
13

__getitem__()

__getitem__() allows an object to support square-bracket indexing.

Custom 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])
Output
Song A
Song C

Python calls __getitem__() when you use:

playlist[0]
14

__setitem__()

The __setitem__() method controls assignment through square brackets.

Changing indexed values
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])
Output
New Song
15

__iter__() and __next__()

Python also uses magic methods to support iteration.

Two important methods are:

  • __iter__() — returns an iterator.
  • __next__() — returns the next item.
Coming next

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.

16

__call__()

The __call__() method allows an object to be called like a function.

Callable objects
class Greeter:

    def __init__(self, name):
        self.name = name

    def __call__(self):
        print(f"Hello, {self.name}!")


greeter = Greeter("Ali")

greeter()
Output
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)
17

__enter__() and __exit__()

Magic methods can also be used to build context managers.

Context managers work with the with statement.

Context manager preview
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.

Concept preview

Context managers are particularly useful for resources such as files, database connections, locks, and network resources.

18

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
19

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.

The goal is not to use as many magic methods as possible. The goal is to make your objects behave naturally.
20

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.

21

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.

22

Mini Challenge — Custom Product Class

Build a professional Product class that uses several magic methods.

Challenge starter
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

  1. Create at least three products.
  2. Test __str__().
  3. Test __repr__().
  4. Compare two products with ==.
  5. Compare products by price.
  6. Use len() to check stock.
  7. Put your products into a list and sort them.
  8. Add another useful magic method if it makes sense.
Think like a Python developer

Do not add magic methods simply to increase the number of methods. Ask whether the behavior is intuitive for someone using your class.

23

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?

24

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 with len().
  • 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.