6.8 Magic Methods (__str__, __repr__, __eq__, etc.) in Python

In Python, special methods (also known as magic methods or dunder methods) are predefined methods that start and end with double underscores (__). These methods enable you to define how your objects behave with built-in operations such as printing, comparison, addition, and more. Special methods allow Python objects to interact with the language in a natural and intuitive way, enhancing flexibility and ease of use.

In this section, we’ll explore some commonly used special methods such as __str__(), __repr__(), __eq__(), and others, along with how to implement and use them in your classes.


6.8.1 What are Special Methods?

Special methods are methods that allow instances of a class to respond to built-in Python operations. They start and end with double underscores (__) and are automatically invoked by Python when certain operations are performed on the object, such as printing, comparing, adding, or representing the object.

Key Special Methods:

  • __str__(): Defines the human-readable string representation of an object (used by print()).
  • __repr__(): Defines the unambiguous string representation of an object (used by repr()).
  • __eq__(): Defines equality comparison between objects (used by ==).
  • __lt__(), __le__(), __gt__(), __ge__(): Define comparison operations (<, <=, >, >=).
  • __add__(): Defines the behavior of the addition operator (+).
  • __len__(): Defines the behavior of the len() function.
  • __getitem__(), __setitem__(): Define indexing and slicing behavior for objects.

6.8.2 The __str__() and __repr__() Methods

Both __str__() and __repr__() define string representations of an object, but they serve different purposes:

  • __str__(): Provides a human-readable or informal string representation of the object, meant to be useful for end users. It’s what is called by print() or str().
  • __repr__(): Provides a developer-friendly, unambiguous string representation of the object, meant for debugging or logging. It’s called by repr() or by default in the interactive interpreter.

Example:

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

    def __str__(self):
        return f"{self.name}, {self.age} years old"

    def __repr__(self):
        return f"Person(name='{self.name}', age={self.age})"

# Creating an instance of Person
person = Person("Alice", 30)

# Printing the person object
print(person)          # Output: Alice, 30 years old (calls __str__)

# Calling repr() on the person object
print(repr(person))    # Output: Person(name='Alice', age=30) (calls __repr__)

In this example:

  • The __str__() method provides a user-friendly string output for the object.
  • The __repr__() method provides a detailed, unambiguous representation that is useful for debugging or logging.

6.8.3 The __eq__() Method

The __eq__() method is used to define the behavior of the equality operator (==) for comparing two objects. By default, Python compares objects by their memory addresses, but you can override this behavior by implementing __eq__() to compare objects based on their attributes.

Example:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

# Creating two Point objects
point1 = Point(2, 3)
point2 = Point(2, 3)
point3 = Point(4, 5)

# Comparing the objects
print(point1 == point2)  # Output: True (calls __eq__)
print(point1 == point3)  # Output: False

In this example:

  • The __eq__() method is implemented to compare two Point objects based on their x and y coordinates.
  • When the == operator is used, it calls __eq__() to check if the points are equal.

6.8.4 Other Comparison Methods (__lt__(), __gt__(), etc.)

Python provides special methods for defining comparison operations between objects:

  • __lt__(self, other): Less than (<).
  • __le__(self, other): Less than or equal (<=).
  • __gt__(self, other): Greater than (>).
  • __ge__(self, other): Greater than or equal (>=).

By implementing these methods, you can control how objects of your class behave when compared using comparison operators.

Example:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def __lt__(self, other):
        return self.area() < other.area()

    def __gt__(self, other):
        return self.area() > other.area()

# Creating two Rectangle objects
rect1 = Rectangle(4, 5)
rect2 = Rectangle(6, 7)

# Comparing the areas of the rectangles
print(rect1 < rect2)  # Output: True (calls __lt__)
print(rect1 > rect2)  # Output: False (calls __gt__)

In this example:

  • The __lt__() and __gt__() methods are implemented to compare two Rectangle objects based on their area.
  • When the < or > operators are used, Python calls these methods to perform the comparison.

6.8.5 The __add__() Method

The __add__() method defines the behavior of the addition operator (+) for your custom objects. By default, Python does not know how to add two objects unless you define this behavior.

Example:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

# Creating two Vector objects
v1 = Vector(1, 2)
v2 = Vector(3, 4)

# Adding two vectors
v3 = v1 + v2
print(v3)  # Output: Vector(4, 6) (calls __add__)

In this example:

  • The __add__() method is implemented to define how two Vector objects are added.
  • When the + operator is used on v1 and v2, Python calls the __add__() method to perform vector addition.

6.8.6 The __len__() Method

The __len__() method defines the behavior of the len() function for your custom objects. It allows you to specify what should be returned when len() is called on an instance of your class.

Example:

class BookCollection:
    def __init__(self, books):
        self.books = books

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

# Creating an instance of BookCollection
collection = BookCollection(["Book 1", "Book 2", "Book 3"])

# Getting the length of the book collection
print(len(collection))  # Output: 3 (calls __len__)

In this example:

  • The __len__() method returns the number of books in the BookCollection.
  • When len(collection) is called, Python invokes the __len__() method to return the number of books.

6.8.7 The __getitem__() and __setitem__() Methods

The __getitem__() and __setitem__() methods define how objects of a class can be indexed or sliced like lists or dictionaries.

  • __getitem__(self, key): Defines behavior for accessing elements using the indexing operator ([]).
  • __setitem__(self, key, value): Defines behavior for assigning values using the indexing operator ([]).

Example:

class CustomList:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return self.data[index]

    def __setitem__(self, index, value):
        self.data[index] = value

# Creating an instance of CustomList
my_list = CustomList([10, 20, 30, 40])

# Accessing and modifying elements using indexing
print

(my_list[2])   # Output: 30 (calls __getitem__)
my_list[2] = 99     # Modifies the third element (calls __setitem__)
print(my_list[2])   # Output: 99

In this example:

  • The __getitem__() method allows access to elements of CustomList using indexing (my_list[2]).
  • The __setitem__() method allows modification of elements using indexing (my_list[2] = 99).

6.8.8 Summary of Special Methods

Special methods allow you to control how your objects behave in various built-in Python operations. Some commonly used special methods include:

  • __str__(): Defines the human-readable string representation (used by print()).
  • __repr__(): Defines the developer-friendly string representation (used by repr()).
  • __eq__(), __lt__(), __gt__(), etc.: Define comparison operations between objects.
  • __add__(): Defines the behavior of the addition operator (+).
  • __len__(): Defines the behavior of the len() function.
  • __getitem__(), __setitem__(): Define behavior for indexing and slicing.

Implementing special methods in your classes allows you to make your objects more intuitive and easier to interact with, as they will behave naturally with common Python operations.


Special methods play a critical role in making your custom classes behave seamlessly with Python’s built-in functions and operators. They provide a powerful way to define the core behavior of your objects and make your classes feel more like native Python data types.