Python Magic Functions: A Comprehensive Guide

In simple terms, Python magic functions (also known as dunder methods) are methods that have double underscores at the beginning and end of their names. When defined inside a class, the Python interpreter automatically invokes them in specific situations. This allows you to customize your classes for various operasions, tailored to your own use cases.

Let’s start with a small example. Suppose we want to make a class iterable so we can loop over its contents. The typical, manual approach might look like this:

team = Team(["alice", "bob", "charlie"])

for member in team.members: print(member)


</div>By implementing the `__getitem__` method, we can turn a class into an iterable (a sequence type). The `item` argument corresponds to the index, and the way it behaves depends on the sequence type you want to mimic (list, dict, tuple).

<div>```
class Team:
    def __init__(self, members):
        self.members = members

    def __getitem__(self, idx):
        return self.members[idx]

team = Team(["alice", "bob", "charlie"])
partial_team = team[:2]

for member in partial_team:
    print(member)

output showing alice and bob

How the Python Data Model Influences Syntax

By using the Python data model (i.e., magic functions) inside a class, you effectively change the language’s syntax for that class. For example, defining __getitem__ turns the class into a sequence, allowing slicing and indexing. Implementing __len__ makes the len() function work on its instances.

def __getitem__(self, idx):
    return self.members[idx]

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

team = Team(["alice", "bob", "charlie"])

print(team[:2]) print(len(team))


</div>Output:

![output showing sliced list and length 3](https://img2018.cnblogs.com/blog/1256425/201812/1256425-20181204221006155-1815133253.png)

### Attribute Interceptor: `__getattr__`

`__getattr__` is only called when an attribute (or method) is not found through normal lookup. If the attribute or method exists on the object, `__getattr__` is not invoked.

<div>```
class ClientProxy:
    @staticmethod
    def as_operator(username):
        return OperatorService().as_operator(username)

    def __getattr__(self, name):
        return getattr(OperatorService(), name)


class OperatorService:

    def __init__(self):
        self.operator = None

    def as_operator(self, operator):
        self.operator = operator
        return self

    def delete_user(self, pk):
        print('delete_user ok')
        return True

    def list_operators(self):
        print('listing operators')


if __name__ == '__main__':
    ClientProxy().as_operator('john').delete_user('123')

output showing delete_user ok

A simple real‑world scenario – data base operations:

class DatabaseInvocation:

def __init__(self, operation):
    self.operation = operation

def __call__(self, *args, **kwargs):
    backend = DatabaseBackend()
    func = getattr(backend, self.operation)
    try:
        return func(*args, **kwargs)
    except Exception as e:
        print(e)

class DatabaseBackend: '''Manage user data in the database'''

def add_user(self):
    print('add_user')

def update_user(self):
    print('update_user')

def remove_user(self):
    print('remove_user')

def fetch_user(self):
    print('fetch_user')

def is_last_admin(self):
    print('is_last_admin')

if name == 'main': DatabaseProxy().remove_user() DatabaseProxy().fetch_user()


</div>Output:

![output showing remove_user and fetch_user](https://img2018.cnblogs.com/blog/1256425/201902/1256425-20190228125528574-690707855.png)

Combining the two patterns:

<div>```
class ClientProxy:
    @staticmethod
    def as_operator(username):
        return OperatorService().as_operator(username)

    def __getattr__(self, name):
        return getattr(OperatorService(), name)


class OperatorService:
    def __init__(self):
        self.operator = None
        self.database = DatabaseProxy()

    def as_operator(self, operator):
        self.operator = operator
        return self

    def add_user(self):
        self.database.add_user()

    def remove_user(self):
        self.database.remove_user()

    def update_user(self):
        self.database.update_user()


class DatabaseProxy:
    def __getattr__(self, operation):
        return DatabaseInvocation(operation)


class DatabaseInvocation:
    def __init__(self, operation):
        self.operation = operation

    def __call__(self, *args, **kwargs):
        backend = DatabaseBackend()
        func = getattr(backend, self.operation)
        try:
            return func(*args, **kwargs)
        except Exception as e:
            raise e


class DatabaseBackend:
    '''Manage user data in the database'''

    def add_user(self):
        print('add_user')

    def update_user(self):
        print('update_user')

    def remove_user(self):
        print('remove_user')

    def fetch_user(self):
        print('fetch_user')


if __name__ == '__main__':
    ClientProxy().as_operator('admin').add_user()
    ClientProxy().as_operator('admin').update_user()
    ClientProxy().as_operator('admin').remove_user()
    ClientProxy().remove_user()

Magic functions can be grouped into two categories: non‑numerical and numerical operations.

Non‑numerical magic functions

list of non-mathematical magic methods

more non-mathematical magic methods

Difference between __str__ and __repr__

Both methods are used for displaying a object. __str__ is intended for end‑users, while __repr__ is aimed at developers. __str__ is called during string formatting, e.g., by print(obj). __repr__ is used in all other contexts, such as in the interactive console or by the built‑in repr() function. When you type an object’s name directly in the interpreter, the displayed description comes from __repr__.

illustration of str vs repr

Numerical magic functions

list of mathematical magic methods

Tags: python dunder-methods magic-methods object-oriented getitem

Posted on Thu, 14 May 2026 06:56:22 +0000 by mwood_2k2