Your Page Title
🔍

    Python Magic Methods

    Magic methods in Python are also called “dunder” methods, which is short for “double underscores.” These are special methods with a specific naming convention: they start and end with double underscores, like _init_, _str_, and _add_. These methods allow you to define behaviors for your classes that integrate seamlessly with Python’s built-in syntax and operations.

    Magic methods should not be invoked directly. Instead, Python will internally invoke the magic method if you call certain things or use particular syntax. This is an in-depth listing of some common magic methods grouped by the purpose that each serves:

    1. Initialization and Representation

    Initialization: __init__

    • Called when an instance of a class is created.
    • Used to initialize the object’s attributes.
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    p = Person("Alice", 30)  # __init__ is called here
    print(p.name)  # Output: Alice

    Representation: __str__ and __repr__

    • __str__: Defines the string representation of an object for users (print or str()).
    • __repr__: Defines the string representation for developers (repr() or interactive shell).
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __str__(self):
            return f"Person: {self.name}, {self.age} years old"
    
        def __repr__(self):
            return f"Person(name='{self.name}', age={self.age})"
    
    p = Person("Alice", 30)
    print(p)  # Output: Person: Alice, 30 years old (from __str__)
    repr(p)   # Output: Person(name='Alice', age=30) (from __repr__)

    2. Arithmetic Operations

    Binary Operators

    • __add__: Implements +
    • __sub__: Implements -
    • __mul__: Implements *
    • __truediv__: Implements /
    • __floordiv__: Implements //
    • __mod__: Implements %
    • __pow__: Implements **
    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 __str__(self):
            return f"Vector({self.x}, {self.y})"
    
    v1 = Vector(2, 3)
    v2 = Vector(4, 5)
    v3 = v1 + v2  # __add__ is called
    print(v3)     # Output: Vector(6, 8)

    Unary Operators

    • __neg__: Implements unary - (negation)
    • __pos__: Implements unary +
    • __abs__: Implements abs()

    3. Comparison Operators

    Comparison Methods

    • __eq__: Implements ==
    • __ne__: Implements !=
    • __lt__: Implements <
    • __le__: Implements <=
    • __gt__: Implements >
    • __ge__: Implements >=
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __eq__(self, other):
            return self.age == other.age
    
    p1 = Person("Alice", 30)
    p2 = Person("Bob", 30)
    print(p1 == p2)  # Output: True (calls __eq__)

    4. Attribute Access

    Custom Attribute Handling

    • getattr: When the attribute is accessed, it is called.
    • setattr: When an attribute is assigned, it is called.
    • delattr: When an attribute is deleted, it is called.
    class Person:
        def __init__(self, name):
            self.name = name
    
        def __getattr__(self, attr):
            return f"{attr} not found"
    
    p = Person("Alice")
    print(p.age)  # Output: age not found (calls __getattr__)

    5. Container Emulation

    Methods for Collections

    • __len__: Implements len()
    • __getitem__: Implements indexing (obj[key])
    • __setitem__: Implements assignment to indexed items (obj[key] = value)
    • __delitem__: Implements item deletion (del obj[key])
    • __contains__: Implements membership test (in)
    class CustomList:
        def __init__(self, data):
            self.data = data
    
        def __len__(self):
            return len(self.data)
    
        def __getitem__(self, index):
            return self.data[index]
    
        def __setitem__(self, index, value):
            self.data[index] = value
    
    my_list = CustomList([1, 2, 3])
    print(len(my_list))   # Output: 3 (calls __len__)
    print(my_list[1])     # Output: 2 (calls __getitem__)
    my_list[1] = 42       # __setitem__ modifies the list
    print(my_list[1])     # Output: 42
    

    6. Callable Objects

    __call__

    • Makes an instance callable like a function.
    class Adder:
        def __init__(self, value):
            self.value = value
    
        def __call__(self, x):
            return self.value + x
    
    add_five = Adder(5)
    print(add_five(10))  # Output: 15 (calls __call__)

    7. Context Management

    __enter__ and __exit__

    • Implemented for use with with statements.
    class FileManager:
        def __init__(self, filename, mode):
            self.file = open(filename, mode)
    
        def __enter__(self):
            return self.file
    
        def __exit__(self, exc_type, exc_value, traceback):
            self.file.close()
    
    with FileManager("test.txt", "w") as f:
        f.write("Hello, World!")

    8. Miscellaneous

    • __iter__: Makes an object iterable.
    • __next__: Implements iteration (next()).
    • __del__: Defines destructor behavior (called when the object is deleted).
    class Counter:
        def __init__(self, low, high):
            self.current = low
            self.high = high
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.current > self.high:
                raise StopIteration
            else:
                self.current += 1
                return self.current - 1
    
    for num in Counter(1, 5):
        print(num)  # Output: 1, 2, 3, 4, 5

    Why Use Magic Methods?

    1. Readable Code: They let your special classes interact smoothly with built-in syntax for things like arithmetic and iteration.
    2. Custom Behavior: Define how your objects behave in specific situations.
    3. Pythonic Design: Leverage the strengths of Python, such as duck typing and operator overloading.