
Python magic methods, also known as special methods or dunder methods (double underscore methods), are powerful tools that allow you to define how your Python objects behave with built-in operations. These Python magic methods enable you to customize fundamental operations like addition, comparison, string representation, and more. Understanding Python magic methods is crucial for creating robust, intuitive classes that integrate seamlessly with Python’s built-in functions and operators.
Python magic methods are automatically invoked by the Python interpreter when specific operations are performed on objects. Every time you use operators like +, -, ==, or functions like len() and str(), Python magic methods are working behind the scenes. By implementing these Python magic methods in your classes, you can make your custom objects behave like built-in Python types.
Python magic methods are special methods that start and end with double underscores (__). These methods define how objects of your class respond to various operations and built-in functions. Python magic methods are automatically called by the Python interpreter - you rarely call them directly.
class SimpleClass:
def __init__(self, value):
self.value = value
def __str__(self):
return f"SimpleClass with value: {self.value}"
obj = SimpleClass(42)
print(obj) # Automatically calls __str__ magic method
The __init__ and __str__ methods shown above are examples of Python magic methods. The __init__ method is called when creating new instances, while __str__ defines how the object appears when converted to a string.
__new__ MethodThe __new__ magic method is responsible for creating new instances of a class. It’s called before __init__ and is particularly useful when you need to control object creation.
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
self.name = name
# Both variables reference the same instance
s1 = Singleton("First")
s2 = Singleton("Second")
print(s1 is s2) # True
__init__ MethodThe __init__ magic method initializes newly created objects. This is the most commonly used Python magic method for setting up instance attributes.
class Student:
def __init__(self, name, age, grades=None):
self.name = name
self.age = age
self.grades = grades or []
self.enrollment_id = id(self)
student = Student("Alice", 20, [85, 92, 78])
__del__ MethodThe __del__ magic method is called when an object is about to be destroyed. It’s useful for cleanup operations like closing files or network connections.
class FileManager:
def __init__(self, filename):
self.filename = filename
self.file = open(filename, 'w')
def __del__(self):
if hasattr(self, 'file') and not self.file.closed:
self.file.close()
print(f"File {self.filename} has been closed")
manager = FileManager("test.txt")
del manager # Triggers __del__ method
__str__ MethodThe __str__ magic method defines the informal string representation of an object, intended for end users. It’s called by str() function and print() statement.
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
return f"'{self.title}' by {self.author} ({self.pages} pages)"
book = Book("1984", "George Orwell", 328)
print(book) # '1984' by George Orwell (328 pages)
__repr__ MethodThe __repr__ magic method provides the official string representation of an object, intended for developers. It should return a string that could recreate the object.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
point = Point(3, 4)
print(repr(point)) # Point(3, 4)
print(str(point)) # (3, 4)
Python magic methods allow you to define how your objects behave with arithmetic operators. These methods make your custom classes work seamlessly with mathematical operations.
__add__ and __radd__ MethodsThe __add__ magic method defines addition behavior, while __radd__ handles reverse addition when the left operand doesn’t support the operation.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
elif isinstance(other, (int, float)):
return Vector(self.x + other, self.y + other)
def __radd__(self, other):
return self.__add__(other)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(1, 4)
print(v1 + v2) # Vector(3, 7)
print(5 + v1) # Vector(7, 8)
__sub__, __mul__, and __truediv__ MethodsThese Python magic methods handle subtraction, multiplication, and division operations respectively.
class Complex:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __sub__(self, other):
return Complex(self.real - other.real, self.imag - other.imag)
def __mul__(self, other):
if isinstance(other, Complex):
real = self.real * other.real - self.imag * other.imag
imag = self.real * other.imag + self.imag * other.real
return Complex(real, imag)
else:
return Complex(self.real * other, self.imag * other)
def __truediv__(self, other):
if isinstance(other, (int, float)):
return Complex(self.real / other, self.imag / other)
def __str__(self):
return f"{self.real} + {self.imag}i"
c1 = Complex(3, 4)
c2 = Complex(1, 2)
print(c1 - c2) # 2 + 2i
print(c1 * 2) # 6 + 8i
Python magic methods for comparison allow you to define how objects are compared using operators like ==, <, >, etc.
__eq__ and __ne__ MethodsThe __eq__ magic method defines equality comparison, while __ne__ defines inequality (though Python automatically provides __ne__ if __eq__ is defined).
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
if isinstance(other, Person):
return self.name == other.name and self.age == other.age
return False
def __hash__(self):
return hash((self.name, self.age))
person1 = Person("John", 25)
person2 = Person("John", 25)
person3 = Person("Jane", 25)
print(person1 == person2) # True
print(person1 == person3) # False
__lt__, __le__, __gt__, __ge__ MethodsThese Python magic methods define less than, less than or equal, greater than, and greater than or equal comparisons.
class Grade:
def __init__(self, score):
self.score = score
def __lt__(self, other):
return self.score < other.score
def __le__(self, other):
return self.score <= other.score
def __gt__(self, other):
return self.score > other.score
def __ge__(self, other):
return self.score >= other.score
def __eq__(self, other):
return self.score == other.score
grade1 = Grade(85)
grade2 = Grade(92)
print(grade1 < grade2) # True
print(grade1 >= grade2) # False
These Python magic methods allow your objects to behave like containers, supporting operations like indexing, slicing, and length checking.
__len__ MethodThe __len__ magic method defines the behavior of the len() function for your objects.
class Playlist:
def __init__(self):
self.songs = []
def add_song(self, song):
self.songs.append(song)
def __len__(self):
return len(self.songs)
def __str__(self):
return f"Playlist with {len(self)} songs"
playlist = Playlist()
playlist.add_song("Song 1")
playlist.add_song("Song 2")
print(len(playlist)) # 2
__getitem__ and __setitem__ MethodsThe __getitem__ magic method enables indexing and slicing, while __setitem__ allows item assignment.
class Matrix:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.data = [[0 for _ in range(cols)] for _ in range(rows)]
def __getitem__(self, key):
row, col = key
return self.data[row][col]
def __setitem__(self, key, value):
row, col = key
self.data[row][col] = value
def __str__(self):
return '\n'.join([' '.join(map(str, row)) for row in self.data])
matrix = Matrix(3, 3)
matrix[0, 0] = 5
matrix[1, 1] = 10
print(matrix[0, 0]) # 5
__contains__ MethodThe __contains__ magic method defines the behavior of the in operator.
class WordSet:
def __init__(self):
self.words = set()
def add_word(self, word):
self.words.add(word.lower())
def __contains__(self, word):
return word.lower() in self.words
word_set = WordSet()
word_set.add_word("Python")
word_set.add_word("Programming")
print("python" in word_set) # True
print("java" in word_set) # False
Python magic methods for iteration allow your objects to be used in loops and with iterator functions.
__iter__ and __next__ MethodsThe __iter__ magic method makes an object iterable, while __next__ defines what happens on each iteration step.
class NumberSequence:
def __init__(self, start, end, step=1):
self.start = start
self.end = end
self.step = step
self.current = start
def __iter__(self):
self.current = self.start
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
else:
self.current += self.step
return self.current - self.step
sequence = NumberSequence(1, 10, 2)
for num in sequence:
print(num, end=" ") # 1 3 5 7 9
Python magic methods for context management enable your objects to work with with statements.
__enter__ and __exit__ MethodsThe __enter__ magic method is called when entering a with block, while __exit__ is called when leaving it.
class DatabaseConnection:
def __init__(self, database_name):
self.database_name = database_name
self.connected = False
def __enter__(self):
print(f"Connecting to {self.database_name}")
self.connected = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Disconnecting from {self.database_name}")
self.connected = False
return False # Don't suppress exceptions
def query(self, sql):
if self.connected:
return f"Executing: {sql}"
else:
return "Not connected to database"
with DatabaseConnection("myapp_db") as db:
result = db.query("SELECT * FROM users")
print(result)
__call__ MethodThe __call__ magic method makes instances of your class callable like functions.
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return value * self.factor
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(4)) # 12
Here’s a comprehensive example demonstrating multiple Python magic methods working together to create a custom number class:
import math
class CustomNumber:
"""A custom number class demonstrating various Python magic methods."""
def __init__(self, value):
"""Initialize CustomNumber with a numeric value."""
if not isinstance(value, (int, float)):
raise TypeError("Value must be numeric")
self.value = value
# String representation magic methods
def __str__(self):
"""Return user-friendly string representation."""
return f"CustomNumber({self.value})"
def __repr__(self):
"""Return developer-friendly string representation."""
return f"CustomNumber({self.value!r})"
# Arithmetic magic methods
def __add__(self, other):
"""Addition operation."""
if isinstance(other, CustomNumber):
return CustomNumber(self.value + other.value)
elif isinstance(other, (int, float)):
return CustomNumber(self.value + other)
return NotImplemented
def __radd__(self, other):
"""Reverse addition operation."""
return self.__add__(other)
def __sub__(self, other):
"""Subtraction operation."""
if isinstance(other, CustomNumber):
return CustomNumber(self.value - other.value)
elif isinstance(other, (int, float)):
return CustomNumber(self.value - other)
return NotImplemented
def __mul__(self, other):
"""Multiplication operation."""
if isinstance(other, CustomNumber):
return CustomNumber(self.value * other.value)
elif isinstance(other, (int, float)):
return CustomNumber(self.value * other)
return NotImplemented
def __truediv__(self, other):
"""Division operation."""
if isinstance(other, CustomNumber):
if other.value == 0:
raise ZeroDivisionError("Cannot divide by zero")
return CustomNumber(self.value / other.value)
elif isinstance(other, (int, float)):
if other == 0:
raise ZeroDivisionError("Cannot divide by zero")
return CustomNumber(self.value / other)
return NotImplemented
def __pow__(self, other):
"""Power operation."""
if isinstance(other, CustomNumber):
return CustomNumber(self.value ** other.value)
elif isinstance(other, (int, float)):
return CustomNumber(self.value ** other)
return NotImplemented
# Comparison magic methods
def __eq__(self, other):
"""Equality comparison."""
if isinstance(other, CustomNumber):
return abs(self.value - other.value) < 1e-10
elif isinstance(other, (int, float)):
return abs(self.value - other) < 1e-10
return False
def __lt__(self, other):
"""Less than comparison."""
if isinstance(other, CustomNumber):
return self.value < other.value
elif isinstance(other, (int, float)):
return self.value < other
return NotImplemented
def __le__(self, other):
"""Less than or equal comparison."""
return self.__lt__(other) or self.__eq__(other)
def __gt__(self, other):
"""Greater than comparison."""
if isinstance(other, CustomNumber):
return self.value > other.value
elif isinstance(other, (int, float)):
return self.value > other
return NotImplemented
def __ge__(self, other):
"""Greater than or equal comparison."""
return self.__gt__(other) or self.__eq__(other)
# Unary operations
def __neg__(self):
"""Negation operation."""
return CustomNumber(-self.value)
def __abs__(self):
"""Absolute value operation."""
return CustomNumber(abs(self.value))
def __round__(self, ndigits=0):
"""Round operation."""
return CustomNumber(round(self.value, ndigits))
# Type conversion magic methods
def __int__(self):
"""Convert to integer."""
return int(self.value)
def __float__(self):
"""Convert to float."""
return float(self.value)
def __bool__(self):
"""Convert to boolean."""
return self.value != 0
# Hash method for use in sets and dictionaries
def __hash__(self):
"""Return hash value."""
return hash(self.value)
# Demonstration of CustomNumber class
if __name__ == "__main__":
# Create CustomNumber instances
num1 = CustomNumber(10.5)
num2 = CustomNumber(3.2)
num3 = CustomNumber(0)
# Test string representations
print(f"String representation: {num1}")
print(f"Repr representation: {repr(num1)}")
# Test arithmetic operations
print(f"\nArithmetic Operations:")
print(f"{num1} + {num2} = {num1 + num2}")
print(f"{num1} - {num2} = {num1 - num2}")
print(f"{num1} * {num2} = {num1 * num2}")
print(f"{num1} / {num2} = {num1 / num2}")
print(f"{num1} ** 2 = {num1 ** 2}")
# Test operations with regular numbers
print(f"{num1} + 5 = {num1 + 5}")
print(f"5 + {num1} = {5 + num1}")
# Test comparison operations
print(f"\nComparison Operations:")
print(f"{num1} == {num2}: {num1 == num2}")
print(f"{num1} > {num2}: {num1 > num2}")
print(f"{num1} < {num2}: {num1 < num2}")
print(f"{num1} >= {num2}: {num1 >= num2}")
# Test unary operations
print(f"\nUnary Operations:")
print(f"Negative of {num1}: {-num1}")
print(f"Absolute of {-num1}: {abs(-num1)}")
print(f"Rounded {num1}: {round(num1)}")
# Test type conversions
print(f"\nType Conversions:")
print(f"Integer value of {num1}: {int(num1)}")
print(f"Float value of {num1}: {float(num1)}")
print(f"Boolean value of {num1}: {bool(num1)}")
print(f"Boolean value of {num3}: {bool(num3)}")
# Test usage in collections
number_set = {num1, num2, CustomNumber(10.5)}
print(f"\nSet with CustomNumbers: {number_set}")
print(f"Length of set (duplicates removed): {len(number_set)}")
# Test in conditional statements
if num1:
print(f"{num1} is truthy")
if not num3:
print(f"{num3} is falsy")
Expected Output:
String representation: CustomNumber(10.5)
Repr representation: CustomNumber(10.5)
Arithmetic Operations:
CustomNumber(10.5) + CustomNumber(3.2) = CustomNumber(13.7)
CustomNumber(10.5) - CustomNumber(3.2) = CustomNumber(7.3)
CustomNumber(10.5) * CustomNumber(3.2) = CustomNumber(33.6)
CustomNumber(10.5) / CustomNumber(3.2) = CustomNumber(3.28125)
CustomNumber(10.5) ** 2 = CustomNumber(110.25)
CustomNumber(10.5) + 5 = CustomNumber(15.5)
5 + CustomNumber(10.5) = CustomNumber(15.5)
Comparison Operations:
CustomNumber(10.5) == CustomNumber(3.2): False
CustomNumber(10.5) > CustomNumber(3.2): True
CustomNumber(10.5) < CustomNumber(3.2): False
CustomNumber(10.5) >= CustomNumber(3.2): True
Unary Operations:
Negative of CustomNumber(10.5): CustomNumber(-10.5)
Absolute of CustomNumber(-10.5): CustomNumber(10.5)
Rounded CustomNumber(10.5): CustomNumber(10)
Type Conversions:
Integer value of CustomNumber(10.5): 10
Float value of CustomNumber(10.5): 10.5
Boolean value of CustomNumber(10.5): True
Boolean value of CustomNumber(0): False
Set with CustomNumbers: {CustomNumber(3.2), CustomNumber(10.5)}
Length of set (duplicates removed): 2
CustomNumber(10.5) is truthy
CustomNumber(0) is falsy
This comprehensive example showcases how Python magic methods enable you to create custom classes that integrate seamlessly with Python’s built-in operations and functions. By implementing these Python magic methods, your objects can behave like native Python types, making your code more intuitive and pythonic.
The CustomNumber class demonstrates practical applications of Python magic methods including arithmetic operations, comparisons, string representations, type conversions, and collection usage. Understanding and implementing these Python magic methods will significantly enhance your object-oriented programming skills and allow you to create more sophisticated, user-friendly classes.