Python Magic Methods: Enhance Your LLM/AI Code

Unlock Python's power with magic (dunder) methods! Learn how to customize behavior and build sophisticated LLM/AI applications like a pro.

Python Magic Methods Explained

Magic methods, often referred to as "dunder" (double underscore) methods, are special methods in Python that are invoked automatically by Python's built-in functions and operators. They begin and end with double underscores (e.g., __init__, __add__). These methods are the key to customizing Python's built-in behaviors, allowing you to make your custom classes behave more like built-in types.

This guide explores some of the most commonly used magic methods with clear examples.

1. Object Initialization and Destruction

These methods control how objects are created and how they are handled when they are no longer needed.

__init__(self, ...) – Constructor

The __init__ method is called automatically when you create a new instance of a class. It's used to initialize the object's attributes.

class Person:
    def __init__(self, name):
        self.name = name

p = Person("Alice")
print(p.name)
# Output: Alice

__del__(self) – Destructor

The __del__ method is called when an object is about to be garbage collected (destroyed). It's less commonly used than __init__ and should be used with caution, as its exact timing is not guaranteed.

class Person:
    def __init__(self, name):
        self.name = name
        print(f"Person object '{self.name}' created.")

    def __del__(self):
        print(f"Person object '{self.name}' is being destroyed.")

p = Person("Bob")
del p  # Explicitly delete the reference, triggering __del__
# Output:
# Person object 'Bob' created.
# Person object 'Bob' is being destroyed.

2. Object Representation

These methods define how your objects are displayed as strings.

__str__(self) – User-Friendly String

The __str__ method returns an "informal" or nicely printable string representation of an object. It's used by functions like print() and str().

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f"'{self.title}' by {self.author}"

my_book = Book("The Hitchhiker's Guide to the Galaxy", "Douglas Adams")
print(my_book)
# Output: 'The Hitchhiker's Guide to the Galaxy' by Douglas Adams

__repr__(self) – Developer-Friendly String

The __repr__ method returns the "official" string representation of an object. It's primarily used for debugging and in the interactive Python shell. The goal is for the output of repr() to be unambiguous and, ideally, to be valid Python code that could recreate the object.

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __repr__(self):
        return f"Book(title='{self.title}', author='{self.author}')"

my_book = Book("1984", "George Orwell")
print(repr(my_book))
# Output: Book(title='1984', author='George Orwell')

Key Difference: __str__ is for human readability, while __repr__ is for unambiguous developer representation. If __str__ is not defined, Python will fall back to using __repr__.

3. Operator Overloading

Magic methods allow your custom objects to respond to standard Python operators.

__add__(self, other) – Addition

This method defines the behavior for the + operator.

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

    def __add__(self, other):
        if isinstance(other, Point):
            return Point(self.x + other.x, self.y + other.y)
        else:
            return NotImplemented # Indicate that addition is not supported with this type

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

p1 = Point(10, 5)
p2 = Point(20, 15)
result = p1 + p2
print(result)
# Output: Point(30, 20)

__eq__(self, other) – Equality

This method defines the behavior for the == operator.

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

    def __eq__(self, other):
        if isinstance(other, Point):
            return self.x == other.x and self.y == other.y
        return False # Not equal if not a Point object

p1 = Point(5, 10)
p2 = Point(5, 10)
p3 = Point(1, 2)

print(p1 == p2)  # Output: True
print(p1 == p3)  # Output: False

Common Operator Overloading Methods

OperationMagic Method
Addition__add__
Subtraction__sub__
Multiplication__mul__
True Division__truediv__
Floor Division__floordiv__
Modulus__mod__
Power__pow__
Less Than__lt__
Less Than or Equal__le__
Greater Than__gt__
Greater Than or Equal__ge__
Equal__eq__
Not Equal__ne__
Assignment Addition__iadd__
......

4. Length, Indexing, and Containment

These methods allow your objects to behave like sequences or collections.

__len__(self) – Length with len()

This method is called by the built-in len() function to get the number of items in a container.

class Team:
    def __init__(self, members):
        self.members = members

    def __len__(self):
        return len(self.members)

team_members = ["Alice", "Bob", "Charlie"]
team = Team(team_members)
print(len(team))
# Output: 3

__getitem__(self, key) – Indexing

This method enables accessing elements using square brackets (e.g., my_list[index]). It's used for sequence-like behavior.

class SquareList:
    def __getitem__(self, index):
        if isinstance(index, int):
            return index * index
        else:
            raise TypeError("Index must be an integer")

squares = SquareList()
print(squares[4])  # Output: 16
print(squares[10]) # Output: 100

__contains__(self, item) – Membership with in

This method defines how the in operator works to check for membership within your object.

class Skills:
    def __init__(self, skills_list):
        self.skills = skills_list

    def __contains__(self, item):
        return item in self.skills

my_skills = Skills(["Python", "SQL", "Data Analysis"])
print("Python" in my_skills)      # Output: True
print("JavaScript" in my_skills)  # Output: False

5. Attribute Management

These methods allow you to intercept attribute access and assignment.

__getattr__(self, name) – Handle Missing Attributes

This method is called only when an attribute lookup has failed to find the attribute in the instance or its class hierarchy. It's often used to provide default values or dynamic attribute access.

class Demo:
    def __getattr__(self, name):
        return f"Attribute '{name}' not found, but I can try to fetch it."

obj = Demo()
print(obj.existing_attribute) # This would raise AttributeError if not for __getattr__
print(obj.abc)
# Output: Attribute 'abc' not found, but I can try to fetch it.

__setattr__(self, name, value) – Custom Attribute Setting

This method is called whenever an attribute is assigned. It allows you to perform custom logic when attributes are set. You must call super().__setattr__(name, value) or self.__dict__[name] = value to actually set the attribute.

class Person:
    def __setattr__(self, name, value):
        print(f"Setting attribute '{name}' to '{value}'...")
        # Ensure the attribute is actually set
        super().__setattr__(name, value)

p = Person()
p.age = 30
# Output: Setting attribute 'age' to '30'...
print(p.age)
# Output: 30

6. Callable Objects and Context Managers

These methods allow objects to be used in more advanced ways, such as being called like functions or managing resources.

__call__(self, ...) – Make an Object Callable

If a class has a __call__ method, instances of that class can be called as if they were functions.

class Multiplier:
    def __call__(self, a, b):
        return a * b

multiply_obj = Multiplier()
result = multiply_obj(4, 5) # Calling the instance
print(result)
# Output: 20

__enter__() and __exit__() – Context Management

These methods are used to implement context managers, which are commonly used with the with statement for resource management (e.g., file handling).

  • __enter__(self): Called when entering the with block. It can return a value that will be bound to the target variable specified in the with statement.
  • __exit__(self, exc_type, exc_value, traceback): Called when exiting the with block. It receives exception information (if an exception occurred) and can handle or suppress exceptions.
class MyContextManager:
    def __enter__(self):
        print("Entering the context...")
        return self # Return an object to be used within the 'with' block

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exiting the context...")
        if exc_type:
            print(f"An exception of type {exc_type.__name__} occurred.")
        # Return True to suppress the exception, False (or None) to re-raise it
        return False # Let exceptions propagate

with MyContextManager() as cm:
    print("Inside the 'with' block.")
    # raise ValueError("Something went wrong!") # Uncomment to test exception handling

# Output:
# Entering the context...
# Inside the 'with' block.
# Exiting the context...

Summary Table of Python Magic Methods

Magic MethodPurpose
__init__Object construction (initialization)
__del__Object destruction
__str__User-readable string representation
__repr__Developer-friendly string representation
__add__, __sub__, etc.Operator overloading (arithmetic)
__lt__, __gt__, etc.Operator overloading (comparison)
__len__Get length of an object (len())
__getitem__Access elements by index/key ([])
__setitem__Assign elements by index/key ([] =)
__contains__Check for membership (in)
__call__Make an object callable as a function
__enter__Entry point for context managers (with)
__exit__Exit point for context managers (with)
__getattr__Handle access to non-existent attributes
__setattr__Intercept attribute assignment
__delattr__Intercept attribute deletion
__getattr__Handle access to non-existent attributes
__setattr__Intercept attribute assignment
__delattr__Intercept attribute deletion

Conclusion

Understanding and utilizing Python's magic methods is fundamental to writing idiomatic and powerful object-oriented Python code. They allow you to define how your custom objects interact with Python's built-in functionalities and operators, making them more intuitive, readable, and powerful. Whether you're implementing custom data structures, creating flexible classes, or extending the behavior of operators, magic methods are your essential tools.


  • Python __init__ vs __new__
  • Operator Overloading in Python
  • __str__ vs __repr__
  • Custom Class Indexing in Python
  • Python __call__ Example
  • Context Manager Magic Methods (__enter__, __exit__)
  • Python Attribute Overloading (__getattr__, __setattr__)
  • Magic Methods for OOP in Python