Python Magic Methods: All you need to know - Part 1

By khoanc, at: Sept. 12, 2023, 11:20 a.m.

Estimated Reading Time: 21 min read

Python Magic Methods: All you need to know - Part 1
Python Magic Methods: All you need to know - Part 1

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."

 


Subscribe

Subscribe to our newsletter and never miss out lastest news.