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 byprint()
).__repr__()
: Defines the unambiguous string representation of an object (used byrepr()
).__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 thelen()
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 byprint()
orstr()
.__repr__()
: Provides a developer-friendly, unambiguous string representation of the object, meant for debugging or logging. It’s called byrepr()
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 twoPoint
objects based on theirx
andy
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 twoRectangle
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 twoVector
objects are added. - When the
+
operator is used onv1
andv2
, 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 theBookCollection
. - 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 ofCustomList
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 byprint()
).__repr__()
: Defines the developer-friendly string representation (used byrepr()
).__eq__()
,__lt__()
,__gt__()
, etc.: Define comparison operations between objects.__add__()
: Defines the behavior of the addition operator (+
).__len__()
: Defines the behavior of thelen()
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.