In Python, magic methods, also known as dunder methods (because they begin and end with double underscores), are a set of predefined methods that allow you to define the behavior of your objects in a variety of situations. These methods are automatically associated with every class definition, and by overriding them, you can customize how your objects behave in various contexts. This flexibility is what makes Python a powerful and highly customizable language.
Why Are They Called Magic?
The term “magic” comes from the fact that these methods provide special functionality and often do so behind the scenes. You don’t usually call these methods directly. Instead, they are invoked by Python in response to certain operations. For instance, when you add two objects with the +
operator, Python automatically invokes the __add__
magic method to perform the operation.
Commonly Used Magic Methods
Let’s explore some of the most useful and commonly employed magic methods:
1. __str__
and __repr__
These methods control how your objects are represented as strings.
__str__
: Defines a human-readable string representation of the object, which is useful when printing the object.__repr__
: Defines an official string representation of the object, which is useful for debugging.
Example:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
return f"{self.title} by {self.author}"
def __repr__(self):
return f"Book('{self.title}', '{self.author}')"
book = Book("1984", "George Orwell")
print(str(book)) # Output: 1984 by George Orwell
print(repr(book)) # Output: Book('1984', 'George Orwell')
2. __getattr__
and __setattr__
These methods control how attributes are accessed on an object.
__getattr__
: Called when accessing an attribute that doesn’t exist on the object.__setattr__
: Called when setting an attribute on the object.
Example:
class Person:
def __init__(self, name):
self.name = name
def __getattr__(self, attr):
return f"{attr} is not found"
def __setattr__(self, attr, value):
print(f"Setting {attr} to {value}")
super().__setattr__(attr, value)
person = Person("Alice")
print(person.name) # Output: Alice
print(person.age) # Output: age is not found
person.name = "Bob" # Output: Setting name to Bob
3. __eq__
, __lt__
, and other comparison methods
These methods enable your objects to be used in expressions such as testing for equality and other comparison operations like greater than or less than.
__eq__
: Defines behavior for the==
operator.__lt__
: Defines behavior for the<
operator.
Example:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def __eq__(self, other):
return self.width * self.height == other.width * other.height
def __lt__(self, other):
return self.width * self.height < other.width * other.height
rect1 = Rectangle(4, 5)
rect2 = Rectangle(2, 10)
rect3 = Rectangle(3, 6)
print(rect1 == rect2) # Output: True
print(rect1 < rect3) # Output: False
4. __call__
This method makes an object callable, just like a function. It allows you to use an instance of a class as if it were a function.
Example:
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
counter = Counter()
print(counter()) # Output: 1
print(counter()) # Output: 2
Conclusion
Magic methods are a key feature that gives Python its flexibility and power. By overriding these methods, you can control how your objects behave in a variety of situations, making your code more intuitive, concise, and readable. Whether you’re customizing how your objects are represented, managing attribute access, enabling comparison operations, or even making objects callable, magic methods open up a world of possibilities for your Python classes.