Special class methods in Python: a complete guide
Special class methods, also known as magic methods or dander methods (from the English double underscore "__"), are a powerful Python tool for customizing the behavior of user classes. These methods allow objects to interact with Python's built-in functions and operators, making the code more readable and pythonic.
What are Python special methods
Special methods are methods that Python calls automatically in certain situations. They provide special behavior for objects, allowing you to define custom actions for standard operations: object initialization, attribute access, comparison operations, arithmetic operations, and more.
Initialization and presentation methods
__init__(self, ...) class constructor
The initialization method is called when creating a new instance of the class. Used to initialize the attributes of an object:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Anna", 25)
print(person.name ) # Conclusion: Anna
__str__(self) - string representation
The method is called by the str() function and print() to get a human-readable string representation of the object:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Dot({self.x}, {self.y})"
point = Point(3, 4)
print(str(point)) # Output: Point(3, 4)
__repr__(self) - formal representation
The method is called by the repr() function to get a formal string representation of the object. Must return a string that is a valid Python expression.:
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"Dot({self.x}, {self.y})"
point = Point(3, 4)
print(repr(point)) # Output: Point(3, 4)
Methods for working with size and content
__len__(self) object length
The method is called by the len() function to get the size of the object.:
class CustomList:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
my_list = CustomList([1, 2, 3, 4, 5])
print(len(my_list)) # Output: 5
__contains__(self, item) verification of ownership
The method is called to check the content of an element in an object using the in operator:
class NumberSet:
def __init__(self, numbers):
self.numbers = set(numbers)
def __contains__(self, item):
return item in self.numbers
number_set = NumberSet([1, 2, 3, 4, 5])
print(3 in number_set) # Output: True
print(6 in number_set) # Output: False
Methods for comparing objects
__eq__(self, other) equality
A method for performing an equality comparison operation (==):
class Student:
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
def __eq__(self, other):
if isinstance(other, Student):
return self.student_id == other.student_id
return False
student1 = Student("Ivan", 123)
student2 = Student("Peter", 123)
print(student1 == student2) # Output: True
Ordinal comparison methods
class Grade:
def __init__(self, value):
self.value = value
def __lt__(self, other):
return self.value < other.value
def __le__(self, other):
return self.value <= other.value
def __gt__(self, other):
return self.value > other.value
def __ge__(self, other):
return self.value >= other.value
def __repr__(self):
return f"Grade({self.value})"
grade1 = Grade(85)
grade2 = Grade(92)
print(grade1< grade2) # Output: True
print(grade1<= grade2) # Output: True
print(grade1> grade2) # Output: False
print(grade1>= grade2) # Output: False
Arithmetic operations
Basic arithmetic operators
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 __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, other):
if isinstance(other, (int, float)):
return Vector(self.x * other, self.y * other)
elif isinstance(other, Vector):
return self.x * other.x + self.y * other.y # Scalar product
def __truediv__(self, other):
if isinstance(other, (int, float)):
return Vector(self.x / other, self.y / other)
raise TypeError("Division is possible only by a number")
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# Usage examples
vector1 = Vector(2, 3)
vector2 = Vector(1, 4)
print(vector1 + vector2) # Output: Vector(3, 7)
print(vector1 - vector2) # Output: Vector(1, -1)
print(vector1 * 2) # Output: Vector(4, 6)
print(vector1 * vector2) # Output: 14
print(vector1 / 2) # Output: Vector(1.0, 1.5)
Methods for accessing elements
__getitem__(self, key) getting the element
class Matrix:
def __init__(self, data):
self.data = data
def __getitem__(self, key):
if isinstance(key, tuple):
row, col = key
return self.data[row][col]
return self.data[key]
def __setitem__(self, key, value):
if isinstance(key, tuple):
row, col = key
self.data[row][col] = value
else:
self.data[key] = value
def __delitem__(self, key):
if isinstance(key, tuple):
row, col = key
del self.data[row][col]
else:
del self.data[key]
matrix = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(matrix[0, 1]) # Output: 2
matrix[1, 2] = 10
print(matrix[1]) # Output: [4, 5, 10]
Methods for creating callable objects
__call__(self, *args, **kwargs)the called object
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)) # Output: 10
print(triple(4)) # Output: 12
Iterators and iteration methods
__iter__(self) and __next__(self) creating iterators
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
self.start -= 1
return self.start + 1
countdown = Countdown(5)
for num in countdown:
print(num)
# Output: 5, 4, 3, 2, 1
# An alternative way is to return a ready-made iterator
class NumberList:
def __init__(self, numbers):
self.numbers = numbers
def __iter__(self):
return iter(self.numbers)
numbers = NumberList([1, 2, 3, 4, 5])
for num in numbers:
print(num)
Context managers
__enter__(self) and __exit__(self, exc_type, exc_value, traceback)
class DatabaseConnection:
def __init__(self, database_name):
self.database_name = database_name
self.connection = None
def __enter__(self):
print(f"Database connection {self.database_name}")
self.connection = f"connection_to_{self.database_name}"
return self.connection
def __exit__(self, exc_type, exc_value, traceback):
print(f"Closing connection with {self.database_name}")
if exc_type:
print(f"Error occurred: {exc_value}")
self.connection = None
return False # We do not suppress exceptions
with DatabaseConnection("mydb") as conn:
print(f"Working with connection: {conn}")
# Output:
# Connecting to the mydb database
# Working with the connection: connection_to_mydb
# Closing the connection with mydb
Additional useful methods
__hash__(self) hashing objects
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
def __repr__(self):
return f"Point({self.x}, {self.y})"
# Now Point can be used in sets and as dictionary keys
points = {Point(1, 2), Point(3, 4), Point(1, 2)}
print(points) # Output: {Point(1, 2), Point(3, 4)}
__bool__(self) - logical value
class Account:
def __init__(self, balance):
self.balance = balance
def __bool__(self):
return self.balance > 0
def __repr__(self):
return f"Account({self.balance})"
account1 = Account(100)
account2 = Account(0)
account3 = Account(-50)
print(bool(account1)) # Output: True
print(bool(account2)) # Output: False
print(bool(account3)) # Output: False
if account1:
print("There are funds in the account")
Practical tips for using
Recommendations for the effective use of special methods:
- Always implement
__repr__for debugging and development - Implement
__str__for human-readable output - When implementing
__eq__, think about implementing__hash__ - Use
isinstance()to check types in comparison methods - Comparison methods should return
NotImplementedfor unsupported types - In context managers, be sure to release resources in
__exit__
Conclusion
Python's special methods provide a powerful way to integrate custom classes with the built-in functions and operators of the language. Using these methods correctly makes the code more readable, intuitive, and consistent with Python principles. Learning and applying magic methods is an important step in mastering object-oriented programming in Python.