Python Magic Methods: All you need to know - Part 1
By khoanc, at: 11:20 Ngày 12 tháng 9 năm 2023
Python, renowned for its simplicity and elegance, conceals a powerful feature set that grants developers fine-grained control over how objects behave. At the heart of this control are Python's "magic methods" or "dunder methods," signified by their double underscores (e.g., __init__
, __str__
). These special methods empower you to define custom behaviors for your Python objects, making them more intuitive and flexible.
In this exploration of Python 3 magic methods, we will reveal their mysteries one by one. These methods play pivotal roles in defining object creation, representation, and even destruction. By understanding and harnessing the power of magic methods, you can craft Python classes that adhere to your precise requirements, elevating your code to new levels of expressiveness and elegance.
1. Initialization and Construction
__new__
Purpose: The __new__
method is called when an object is created. It is responsible for creating and returning a new instance of the class. It is often used in situations where you need to customize the object creation process before it is initialized by __init__
.
Example
class MySingleton:
instance = None
# Class attribute to store the single instance
def __new__(cls):
if cls.instance is None:
cls.instance = super(MySingleton, cls).__new__(cls)
return cls.instance
obj1 = MySingleton()
obj2 = MySingleton() # Both obj1 and obj2 are the same instance print(obj1 is obj2) # Output: True
__init__
Purpose: The __init__
method is called after the __new__
method and is responsible for initializing the attributes of the object. It allows you to set up the object's initial state.
Example
class Person:
def __init__(self, name):
self.name = name
obj = Person("Alice")
print(obj.name) # Output: "Alice"
__del__
Purpose: The __del__
method, also known as the destructor, is called when an object is about to be destroyed or deleted. It can be used to perform cleanup operations or release resources associated with the object.
Example
class Appartment:
def __init__(self, name):
self.name = name
def __del__(self):
print(f"Resource {self.name} is being destroyed.")
obj = Appartment("Wonder 7")
del obj # Output: "Resource Wonder 7 is being destroyed."
2. Numeric magic methods
__trunc__
Purpose: Implements the behavior for the math.trunc() function, which truncates a floating-point number towards zero.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __trunc__(self):
return int(self.value)
num = CustomNumber(4.9)
result = math.trunc(num)
print(result) # Output: 4
__ceil__
Purpose: Implements the behavior for math.ceil(), which rounds a number up to the nearest integer.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __ceil__(self):
return math.ceil(self.value)
num = CustomNumber(3.2)
result = math.ceil(num)
print(result) # Output: 4
__floor__
Purpose: Implements the behavior for math.floor(), which rounds a number down to the nearest integer.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __floor__(self):
return math.floor(self.value)
num = CustomNumber(3.8)
result = math.floor(num)
print(result) # Output: 3
__round__
Purpose: Implements the behavior for the built-in round() function, allowing custom rounding with the specified number of decimal places (n).
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __round__(self, n):
return round(self.value, n)
num = CustomNumber(3.14159265)
result = round(num, 2)
print(result) # Output: 3.14
__invert__
Purpose: Implements the behavior for inversion using the ~ operator (bitwise NOT) when applied to an object.
Example
class MyBitwise:
def __init__(self, value):
self.value = value
def __invert__(self):
return ~self.value
bitwise_obj = MyBitwise(5)
result = ~bitwise_obj
print(result) # Output: -6 (bitwise NOT of 5)
__abs__
Purpose: Implements the behavior for the built-in abs() function, returning the absolute value of an object.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __abs__(self):
return abs(self.value)
num = CustomNumber(-7)
result = abs(num)
print(result) # Output: 7
__neg__
Purpose: Implements behavior for negation (unary -) when applied to an object.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __neg__(self):
return -self.value
num = CustomNumber(10)
result = -num
print(result) # Output: -10
__pos__
Purpose: Implements behavior for unary positive (+) when applied to an object.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __pos__(self):
return +self.value
num = CustomNumber(10)
result = +num
print(result) # Output: 10
3. Arithmetic operators
Python allows you to define custom behaviors for arithmetic operations using magic methods. These methods enable your objects to participate in mathematical calculations seamlessly. Here are the essential arithmetic magic methods:
__add__
Purpose: Implements behavior for the addition (+) operator when used between objects.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __add__(self, other):
return CustomNumber(self.value + other.value)
num1 = CustomNumber(5)
num2 = CustomNumber(3)
result = num1 + num2
print(result.value) # Output: 8
__sub__
Purpose: Implements behavior for the subtraction (-) operator when used between objects.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __sub__(self, other):
return CustomNumber(self.value - other.value)
num1 = CustomNumber(8)
num2 = CustomNumber(3)
result = num1 - num2
print(result.value) # Output: 5
__mul__
Purpose: Implements behavior for the multiplication (*) operator when used between objects.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __mul__(self, other):
return CustomNumber(self.value * other.value)
num1 = CustomNumber(4)
num2 = CustomNumber(7)
result = num1 * num2
print(result.value) # Output: 28
__floordiv__
Purpose: Implements behavior for the floor division (//) operator when used between objects.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __floordiv__(self, other):
return CustomNumber(self.value // other.value)
num1 = CustomNumber(10)
num2 = CustomNumber(3)
result = num1 // num2
print(result.value) # Output: 3
__div__
Purpose: Implements behavior for the division (/) operator when used between objects (Python 2 only).
Example (Python 2)
class CustomNumber:
def __init__(self, value):
self.value = value
def __div__(self, other):
return CustomNumber(self.value / other.value)
num1 = CustomNumber(8)
num2 = CustomNumber(2)
result = num1 / num2
print(result.value) # Output: 4.0
__truediv__
Purpose: Implements behavior for the true division (/) operator when used between objects (Python 3+).
Example (Python 3+)
class CustomNumber:
def __init__(self, value):
self.value = value
def __truediv__(self, other):
return CustomNumber(self.value / other.value)
num1 = CustomNumber(8)
num2 = CustomNumber(2)
result = num1 / num2
print(result.value) # Output: 4.0
__mod__
Purpose: Implements behavior for the modulo (%) operator when used between objects.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __mod__(self, other):
return CustomNumber(self.value % other.value)
num1 = CustomNumber(10)
num2 = CustomNumber(3)
result = num1 % num2
print(result.value) # Output: 1
__divmod__
Purpose: Implements behavior for the built-in divmod() function when used between objects.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __divmod__(self, other):
return divmod(self.value, other.value)
num1 = CustomNumber(10)
num2 = CustomNumber(3)
result = divmod(num1, num2)
print(result) # Output: (3, 1)
__pow__
Purpose: Implements behavior for exponentiation (**) when used between objects.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __pow__(self, other):
return CustomNumber(self.value ** other.value)
num1 = CustomNumber(2)
num2 = CustomNumber(3)
result = num1 ** num2
print(result.value) # Output: 8
__lshift__
Purpose: Implements behavior for left bitwise shift (<<) when used between objects.
Example
class MyBitwise:
def __init__(self, value):
self.value = value
def __lshift__(self, other):
return MyBitwise(self.value << other.value)
bitwise_obj1 = MyBitwise(8)
bitwise_obj2 = MyBitwise(2)
result = bitwise_obj1 << bitwise_obj2
print(result.value) # Output: 32 (8 << 2)
__rshift__
Purpose: Implements behavior for right bitwise shift (>>) when used between objects.
Example
class MyBitwise:
def __init__(self, value):
self.value = value
def __rshift__(self, other):
return MyBitwise(self.value >> other.value)
bitwise_obj1 = MyBitwise(16)
bitwise_obj2 = MyBitwise(2)
result = bitwise_obj1 >> bitwise_obj2
print(result.value) # Output: 4 (16 >> 2)
__and__
Purpose: Implements behavior for bitwise AND (&) when used between objects.
Example
class MyBitwise:
def __init__(self, value):
self.value = value
def __and__(self, other):
return MyBitwise(self.value & other.value)
bitwise_obj1 = MyBitwise(5)
bitwise_obj2 = MyBitwise(3)
result = bitwise_obj1 & bitwise_obj2
print(result.value) # Output: 1 (5 & 3)
__or__
Purpose: Implements behavior for bitwise OR (|) when used between objects.
Example
class MyBitwise:
def __init__(self, value):
self.value = value
def __or__(self, other):
return MyBitwise(self.value | other.value)
bitwise_obj1 = MyBitwise(5)
bitwise_obj2 = MyBitwise(3)
result = bitwise_obj1 | bitwise_obj2
print(result.value) # Output: 7 (5 | 3)
__xor__
Purpose: Implements behavior for bitwise XOR (^) when used between objects.
Example
class MyBitwise:
def __init__(self, value):
self.value = value
def __xor__(self, other):
return MyBitwise(self.value ^ other.value)
bitwise_obj1 = MyBitwise(5)
bitwise_obj2 = MyBitwise(3)
result = bitwise_obj1 ^ bitwise_obj2
print(result.value) # Output: 6 (5 ^ 3)
__bool__
Purpose: define the custom behavior of an object when it is evaluated in a boolean context using the bool() function or in conditions like if statements. This method should return either True (if the object is considered "truthy") or False (if the object is considered "falsy").
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __bool__(self):
# Define the custom boolean behavior.
return self.value != 0
# Create instances of CustomNumber with different values.
num1 = CustomNumber(5) # This instance is considered "truthy."
num2 = CustomNumber(0) # This instance is considered "falsy."
# Use the instances in boolean contexts.
if num1:
print("num1 is considered 'truthy'.")
else:
print("num1 is considered 'falsy'.")
if num2:
print("num2 is considered 'truthy'.")
else:
print("num2 is considered 'falsy'.")
4. String Magic Methods
Python provides a set of magic methods that allow you to customize the behavior of your objects when they are converted to strings, represented, or used in various string-related operations. Here are some essential string-related magic methods:
__str__
Purpose: Defines the behavior for when str() is called on an instance of your class. It should return a human-readable string representation of the object.
Example
class CustomString:
def __init__(self, value):
self.value = value
def __str__(self):
return f"CustomString instance with value: {self.value}"
obj = CustomString(42)
print(str(obj)) # Output: "CustomString instance with value: 42"
__repr__
Purpose: To be called by the built-in repr() method to return a machine-readable representation of the object's type.
Example
class CustomString:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"CustomString({self.value})"
obj = CustomString(42)
print(repr(obj)) # Output: "CustomString(42)"
__unicode__
Purpose: This method returns a Unicode string representation of the object.
Example
class CustomString:
def __init__(self, value):
self.value = value
def __unicode__(self):
return unicode(self.value)
obj = CustomString(u'Hello')
print(unicode(obj)) # Output: u'Hello'
__format__
Purpose: Allows custom formatting when using the new-style string formatting.
Example
class CustomDate:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __format__(self, formatstr):
if formatstr == 'short':
return f'{self.month}/{self.day}/{self.year}'
elif formatstr == 'long':
return f'{self.year}-{self.month:02d}-{self.day:02d}'
else:
return str(self)
date = CustomDate(2023, 9, 20)
print(f'Short format: {date:short}') # Output: "9/20/2023"
print(f'Long format: {date:long}') # Output: "2023-09-20"
__hash__
Purpose: Returns an integer that is used for quick key comparison in dictionaries and sets. This method is crucial when you want instances of your class to be hashable.
Example
class CustomClass:
def __init__(self, value):
self.value = value
def __hash__(self):
return hash(self.value)
obj1 = CustomClass(42)
obj2 = CustomClass(42)
my_dict = {obj1: 'Value 1', obj2: 'Value 2'}
print(my_dict[obj1]) # Output: "Value 1"
__dir__
Purpose: This method should return a list of attributes and methods of a class when dir() is called on an instance.
Example
class CustomClass:
def __init__(self, value):
self.value = value
def custom_method(self):
return self.value
obj = CustomClass(42)
attributes = dir(obj)
print(attributes) # Output: ['__class__', '__delattr__', '__dict__', ... 'value', 'custom_method', ...]
__sizeof__
Purpose: Returns the size of the object in memory, in bytes.
Example
class CustomClass:
def __init__(self, value):
self.value = value
obj = CustomClass(42)
size = sys.getsizeof(obj)
print(size)
Comparison magic methods
Python provides a set of magic methods that allow you to define custom behavior for comparison operators, enabling your objects to participate in comparisons. Here are the essential comparison magic methods:
__eq__
Purpose: Defines behavior for the equality operator (==). It should return True if the object is equal to the other object, False otherwise.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other.value
num1 = CustomNumber(5)
num2 = CustomNumber(5)
result = num1 == num2
print(result) # Output: True
__ne__
Purpose: Defines behavior for the inequality operator (!=). It should return True if the object is not equal to the other object, False otherwise.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __ne__(self, other):
return self.value != other.value
num1 = CustomNumber(5)
num2 = CustomNumber(7)
result = num1 != num2
print(result) # Output: True
__lt__
Purpose: Defines behavior for the less-than operator (<). It should return True if the object is less than the other object, False otherwise.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __lt__(self, other):
return self.value < other.value
num1 = CustomNumber(5)
num2 = CustomNumber(7)
result = num1 < num2
print(result) # Output: True
__gt__
Purpose: Defines behavior for the greater-than operator (>). It should return True if the object is greater than the other object, False otherwise.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __gt__(self, other):
return self.value > other.value
num1 = CustomNumber(5)
num2 = CustomNumber(7)
result = num1 > num2
print(result) # Output: False
__le__
Purpose: Defines behavior for the less-than-or-equal-to operator (<=). It should return True if the object is less than or equal to the other object, False otherwise.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __le__(self, other):
return self.value <= other.value
num1 = CustomNumber(5)
num2 = CustomNumber(5)
result = num1 <= num2
print(result) # Output: True
__ge__
Purpose: Defines behavior for the greater-than-or-equal-to operator (>=). It should return True if the object is greater than or equal to the other object, False otherwise.
Example
class CustomNumber:
def __init__(self, value):
self.value = value
def __ge__(self, other):
return self.value >= other.value
num1 = CustomNumber(7)
num2 = CustomNumber(5)
result = num1 >= num2
print(result) # Output: True
5. Encapsulation “magic” methods
Python provides two "magic" methods, __setattr__
and __getattr__
, that allow you to customize attribute access and modification for instances of your class, enabling encapsulation and control over attribute behavior.
__setattr__
Purpose: This method is called when an attempt is made to set an attribute's value using the assignment operator (=). It allows you to customize how attribute assignments are handled and implement encapsulation and validation logic.
Example
class Person:
def __init__(self):
self._value = None # Initialize a protected attribute with a default value.
def __setattr__(self, name, value):
if name == '_value':
if value < 0:
raise ValueError("Value cannot be negative.")
super().__setattr__(name, value) # Use super() to set the attribute to avoid recursion.
obj = Person()
obj._value = 42 # This will work because of the custom __setattr__ method.
obj._value = -5 # This will raise a ValueError.
__getattr__
Purpose: This method is called when an attempt is made to access an attribute that does not exist. It allows you to customize attribute access behavior and handle attribute "get" operations dynamically.
Example
class Person:
def __getattr__(self, name):
return f"Attribute '{name}' does not exist."
obj = Person()
print(obj.some_attribute)
__call__
Purpose: Make an instance of a class callable like a function. When an object of a class defines __call__, you can use parentheses to invoke the object as if it were a function.
Example
class MyCallable:
def __init__(self):
self.counter = 0
def __call__(self):
# Define the behavior when the instance is called.
self.counter += 1
return f"Object called {self.counter} times."
# Create an instance of MyCallable.
callable_obj = MyCallable()
# Call the instance as if it were a function.
result1 = callable_obj()
result2 = callable_obj()
print(result1) # Output: "Object called 1 times."
print(result2) # Output: "Object called 2 times."