Fundamentals 11 min read

Understanding Python Magic (Dunder) Methods

This article explains Python's magic (dunder) methods, categorizing them by purpose—construction, representation, comparison, arithmetic, container behavior, callability, and context management—and provides clear code examples illustrating how to customize class behavior for more Pythonic, intuitive objects.

php中文网 Courses
php中文网 Courses
php中文网 Courses
Understanding Python Magic (Dunder) Methods

Magic methods (also called special methods or dunder methods) are a set of special functions in Python that start and end with double underscores (e.g., __init__ ). These methods are automatically invoked by the Python interpreter in specific situations without being called directly.

Magic methods provide powerful capabilities for Python's object‑oriented programming, allowing developers to customize class behavior so that instances behave like built‑in types.

Common Magic Method Categories

1. Construction and Initialization

__new__(cls[, ...]) : the first method called when creating an instance.

__init__(self[, ...]) : instance initialization method.

__del__(self) : destructor method, called when an instance is destroyed.

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("__new__ called")
        instance = super().__new__(cls)
        return instance

    def __init__(self, value):
        print("__init__ called")
        self.value = value

    def __del__(self):
        print("__del__ called")

obj = MyClass(10)  # Output: __new__ called then __init__ called
del obj  # Output: __del__ called

2. Object Representation

__str__(self) : defines behavior for str() or print() .

__repr__(self) : defines behavior for repr() , usually for debugging.

__format__(self, format_spec) : defines behavior for format() .

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

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

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

p = Point(1, 2)
print(str(p))   # Output: Point(1, 2)
print(repr(p))  # Output: Point(x=1, y=2)

3. Comparison Operators

__eq__(self, other) : defines behavior for == .

__ne__(self, other) : defines behavior for != .

__lt__(self, other) : defines behavior for < .

__le__(self, other) : defines behavior for <= .

__gt__(self, other) : defines behavior for > .

__ge__(self, other) : defines behavior for >= .

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

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

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

s1 = Student("Alice", 90)
s2 = Student("Bob", 85)
print(s1 == s2)  # Output: False
print(s1 < s2)   # Output: False

4. Arithmetic Operators

__add__(self, other) : defines behavior for + .

__sub__(self, other) : defines behavior for - .

__mul__(self, other) : defines behavior for * .

__truediv__(self, other) : defines behavior for / .

__floordiv__(self, other) : defines behavior for // .

__mod__(self, other) : defines behavior for % .

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(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)  # Output: Vector(4, 6)

5. Container Type Methods

__len__(self) : defines behavior for len() .

__getitem__(self, key) : defines element retrieval ( self[key] ).

__setitem__(self, key, value) : defines element assignment ( self[key] = value ).

__delitem__(self, key) : defines element deletion ( del self[key] ).

__contains__(self, item) : defines behavior for in operator.

class ShoppingCart:
    def __init__(self):
        self.items = []

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

    def __getitem__(self, index):
        return self.items[index]

    def __setitem__(self, index, value):
        self.items[index] = value

    def __delitem__(self, index):
        del self.items[index]

    def add_item(self, item):
        self.items.append(item)

cart = ShoppingCart()
cart.add_item("Apple")
cart.add_item("Banana")
print(len(cart))   # Output: 2
print(cart[1])      # Output: Banana

6. Callable Objects

__call__(self[, args...]) : makes an instance callable like a function.

class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        return self.n + x

add5 = Adder(5)
print(add5(10))  # Output: 15

7. Context Management

__enter__(self) : defines behavior at the start of a with block.

__exit__(self, exc_type, exc_val, exc_tb) : defines behavior at the end of a with block.

class Timer:
    def __enter__(self):
        import time
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        self.end = time.time()
        print(f"Elapsed: {self.end - self.start:.2f} seconds")

with Timer():
    # Execute some code
    sum(range(1000000))

Practical Applications of Magic Methods

1. Implementing a Decorator Class

class Logger:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f"Calling function: {self.func.__name__}")
        result = self.func(*args, **kwargs)
        print(f"Function returned: {result}")
        return result

@Logger
def add(a, b):
    return a + b

print(add(2, 3))
# Output:
# Calling function: add
# Function returned: 5
# 5

2. Implementing Attribute Access Control

class Protected:
    def __init__(self):
        self._protected_data = "protected data"
        self.__private_data = "private data"

    def __getattribute__(self, name):
        print(f"Attempting to access attribute: {name}")
        return super().__getattribute__(name)

    def __setattr__(self, name, value):
        print(f"Setting attribute: {name} = {value}")
        super().__setattr__(name, value)

    def __delattr__(self, name):
        print(f"Deleting attribute: {name}")
        super().__delattr__(name)

p = Protected()
print(p._protected_data)
p.new_attr = "new attribute"
del p.new_attr

3. Implementing the Iterator Pattern

class Countdown:
    def __init__(self, start):
        self.start = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.start <= 0:
            raise StopIteration
        current = self.start
        self.start -= 1
        return current

for num in Countdown(5):
    print(num, end=" ")
# Output: 5 4 3 2 1

Notes

Do not overuse magic methods; only implement them when you truly need to change default behavior.

Maintain consistency: when implementing comparison operators, provide the full set or use functools.total_ordering .

Performance considerations: magic method calls add overhead; be cautious in performance‑critical code.

Documentation: clearly describe custom magic method behavior in your class docs.

Summary

Python's magic methods give object‑oriented programming great flexibility, allowing developers to customize class behavior in many situations. By using them wisely, you can create intuitive, Pythonic classes, but they should be applied judiciously.

pythonprogrammingobject-orientedMagic MethodsDunder Methods
php中文网 Courses
Written by

php中文网 Courses

php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.