๐Ÿ“โœจ Magic Methods in Python โœจ#

Magic methods (also known as dunder methods) are special methods in Python that begin and end with double underscores (e.g., init, str, len). They enable you to integrate your classes seamlessly with Pythonโ€™s built-in operations, such as arithmetic, string conversion, comparison, and iteration.

What Are Magic Methods?#

  • Definition: Predefined method names recognized by Python to perform specific tasks or enable specific syntactic sugar.

  • Naming: Always starts and ends with __ (double underscores).

  • Examples: init, str, repr, add, len, eq, iter, etc.

class Example:
    def __init__(self):
        print("Object created!")


obj = Example()  # Automatically calls __init__
Object created!

Common Magic Methods#

  1. Object Construction & Representation

    • init(self, ...): Called after instance creation to initialize the object.

    • del(self): Called when the object is about to be destroyed (use cautiously).

    • repr(self): Returns an official string representation of the object (for developers).

    • str(self): Returns a readable string representation of the object (for end users).

  1. Arithmetic & Comparison

    • add(self, other): Defines the + operator.

    • sub(self, other): Defines the - operator.

    • mul(self, other), truediv(self, other), etc.

    • eq(self, other), lt(self, other), gt(self, other): Comparison operators.

  1. Container & Sequence Protocols

    • len(self): Returns the length (used by len() function).

    • getitem(self, index): Enables indexing or slicing.

    • setitem(self, index, value): Enables assignment to an index.

    • iter(self): Returns an iterator object, allowing iteration over items (for loops).

  1. Context Manager Protocol

    • enter(self): Code that runs upon entering a with block.

    • exit(self, exc_type, exc_val, exc_tb): Code that runs upon exiting a with block.

Object Construction & Representation#

str vs. repr#

  • str should return a human-readable string.

  • repr is meant for unambiguous representation (often used for debugging).

Example

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"
  • print(Point(1, 2)) will call str โ†’ (1, 2)

  • Point(1,2) in a Python REPL returns repr โ†’ Point(x=1, y=2)

Arithmetic & Comparison Methods#

Arithmetic (+, -, *, /, etc.)#

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector2D(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector2D(self.x - other.x, self.y - other.y)

    def __repr__(self):
        return f"Vector2D({self.x}, {self.y})"


v1 = Vector2D(2, 3)
v2 = Vector2D(5, 7)
print(v1 + v2)  # Vector2D(7, 10)
print(v1 - v2)  # Vector2D(-3, -4)
Vector2D(7, 10)
Vector2D(-3, -4)

Comparison (==, <, >, etc.)#

class Item:
    def __init__(self, weight):
        self.weight = weight

    def __eq__(self, other):
        return self.weight == other.weight

    def __lt__(self, other):
        return self.weight < other.weight

Container & Sequence Protocols#

  1. len(self): Defines the behavior of len(obj).

  1. getitem(self, index): Access elements like obj[index].

  1. iter(self): Allows the object to be iterated in a for loop.

Example

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 __iter__(self):
        return iter(self.data)


numbers = CustomList([1, 2, 3, 4])
print(len(numbers))  # 4
print(numbers[2])  # 3
for num in numbers:
    print(num)  # Iterates 1, 2, 3, 4
4
3
1
2
3
4

Context Manager Protocol#

  • enter(self): Called at the start of a with block.

  • exit(self, exc_type, exc_val, exc_tb): Called at the end, even if an exception occurred.

Example

class FileOpener:
    def __init__(self, filename):
        self.filename = filename

    def __enter__(self):
        self.file = open(self.filename, "w")
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()


with FileOpener("demo.txt") as f:
    f.write("Hello, world!")
# File is automatically closed when exiting 'with' block

Best Practices & Tips#

  1. Use Magic Methods Judiciously

    • They can make code more Pythonic but can also confuse readers if overused or used in non-intuitive ways.

  1. Provide Clear Behavior

    • If you overload add, make sure it logically represents addition in your context.

  1. Maintain Readability

    • Overriding too many magic methods can obscure whatโ€™s happening behind the scenes.

  1. Use repr for Debugging

    • Provide enough information to identify the object state.

  1. Test Thoroughly

    • When implementing custom arithmetic or iteration logic, ensure you handle edge cases (type checks, boundaries, etc.).

Use magic methods to write more Pythonic, elegant code that integrates seamlessly with Pythonโ€™s built-in operations and idioms.