Magic Methods in Python

Magic Methods in Python

This article explains magic methods in Python.

YouTube Video

Magic Methods in Python

Magic methods (special methods) in Python are specially named methods used to give classes unique behaviors. For example, they are characterized by being enclosed in double underscores (dunders), such as __init__ and __str__. Therefore, they are also called dunder methods.

By defining magic methods, you can customize the standard behavior of classes. For example, defining __add__ allows you to change the behavior of the + operator.

Magic Methods for Initialization and String Representation

__init__: Initialization Method

The __init__ method is a constructor automatically called when an instance is created.

1class Person:
2    def __init__(self, name, age):
3        self.name = name
4        self.age = age
5
6person = Person("Alice", 30)
7print(person.name)  # Alice

__str__: Human-Readable String Representation

The __str__ method returns a human-readable string representation. It is called by print() and str().

1class Person:
2    def __init__(self, name):
3        self.name = name
4
5    def __str__(self):
6        return f"Person: {self.name}"
7
8person = Person("Bob")
9print(person)  # Person: Bob

__repr__: Debug-Oriented String Representation

The __repr__ method returns a string representation for debugging purposes. It is used by repr() and when displaying in the interpreter.

1class Person:
2    def __init__(self, name):
3        self.name = name
4
5    def __repr__(self):
6        return f"Person(name={self.name!r})"
7
8person = Person("Eve")
9print(repr(person))  # Person(name='Eve')

Overloading Arithmetic Operators

__add__: + Operator

By using magic methods, arithmetic operators can be overloaded. For example, the __add__ method allows overloading of the + operator.

 1class Vector:
 2    def __init__(self, x, y):
 3        self.x = x
 4        self.y = y
 5
 6    def __add__(self, other):
 7        return Vector(self.x + other.x, self.y + other.y)
 8
 9    def __repr__(self):
10        return f"Vector({self.x}, {self.y})"
11
12    # Other arithmetic magic methods:
13    # def __sub__(self, other):       # Subtraction (-)
14    # def __mul__(self, other):       # Multiplication (*)
15    # def __truediv__(self, other):   # True division (/)
16    # def __floordiv__(self, other):  # Floor division (//)
17    # def __mod__(self, other):       # Modulo (%)
18    # def __pow__(self, other):       # Exponentiation (**)
19
20v1 = Vector(1, 2)
21v2 = Vector(3, 4)
22print(v1 + v2)  # Vector(4, 6)

Other arithmetic magic methods include the following:.

Method Operator
__add__ +
__sub__ -
__mul__ *
__truediv__ /
__floordiv__ //
__mod__ %
__pow__ **

Customizing Comparison Operators

You can also overload comparison operators. For example, the __eq__ method overloads the equality operator.

 1class Box:
 2    def __init__(self, volume):
 3        self.volume = volume
 4
 5    def __eq__(self, other):
 6        return self.volume == other.volume
 7
 8    def __lt__(self, other):
 9        return self.volume < other.volume
10
11    # Comparison magic methods:
12    # def __eq__(self, other):  # Equal to (==)
13    # def __ne__(self, other):  # Not equal to (!=)
14    # def __lt__(self, other):  # Less than (<)
15    # def __le__(self, other):  # Less than or equal to (<=)
16    # def __gt__(self, other):  # Greater than (>)
17    # def __ge__(self, other):  # Greater than or equal to (>=)
18
19b1 = Box(100)
20b2 = Box(200)
21
22print(b1 == b2)  # False
23print(b1 < b2)   # True

There are also the following magic methods:. These magic methods define comparisons such as equality and ordering between objects.

Operator Magic Method
== __eq__
!= __ne__
< __lt__
<= __le__
> __gt__
>= __ge__

Magic Methods for Container Types

You can create classes that behave like lists or dictionaries.

__len__: Getting the Number of Elements

The __len__ method is a magic method that returns the number of elements.

1class Basket:
2    def __init__(self, items):
3        self.items = items
4
5    def __len__(self):
6        return len(self.items)
7
8basket = Basket(["apple", "banana"])
9print(len(basket))  # 2

__getitem__: Index Access

The __getitem__ method is called when using index access.

1class Basket:
2    def __init__(self, items):
3        self.items = items
4
5    def __getitem__(self, index):
6        return self.items[index]
7
8basket = Basket(["apple", "banana"])
9print(basket[1])  # banana

__setitem__ and __delitem__: Writing and Deleting

The __setitem__ and __delitem__ methods are called when writing or deleting items.

 1class Basket:
 2    def __init__(self, items):
 3        self.items = items
 4
 5    def __setitem__(self, index, value):
 6        self.items[index] = value
 7
 8    def __delitem__(self, index):
 9        del self.items[index]
10
11basket = Basket(["apple", "banana"])
12basket[1] = "grape"
13del basket[0]
14print(basket.items)  # ['grape']

Magic Methods for Iteration

To make an object iterable in a for loop, implement the following:.

__iter__ and __next__

 1class Counter:
 2    def __init__(self, limit):
 3        self.limit = limit
 4        self.current = 0
 5
 6    def __iter__(self):
 7        return self
 8
 9    def __next__(self):
10        if self.current >= self.limit:
11            raise StopIteration
12        self.current += 1
13        return self.current
14
15for num in Counter(3):
16    print(num)
17# Output:
18# 1
19# 2
20# 3

Context Manager (with Statement)

By defining __enter__ and __exit__ methods, you can implement pre- and post-processing for the with statement.

 1class FileOpener:
 2    def __init__(self, filename):
 3        self.filename = filename
 4
 5    def __enter__(self):
 6        self.file = open(self.filename, 'r')
 7        return self.file
 8
 9    def __exit__(self, exc_type, exc_val, exc_tb):
10        self.file.close()
11
12with FileOpener("example.txt") as f:
13    content = f.read()
14    print(content)

Callable Objects: __call__

By defining the __call__ method, you can make an instance callable like a function.

1class Greeter:
2    def __init__(self, greeting):
3        self.greeting = greeting
4
5    def __call__(self, name):
6        return f"{self.greeting}, {name}!"
7
8hello = Greeter("Hello")
9print(hello("Alice"))  # Hello, Alice!

Summary

Magic methods in Python are a powerful way to add natural and intuitive behavior to classes. Each method has a clear purpose, and implementing them properly allows you to write more flexible and Pythonic code.

Magic methods serve as an 'invisible interface' that supports how Python objects interact and behave behind the scenes.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video