pvdocs /Python/Dunder Methods

Dunder Methods

Dunder (double-underscore) methods, also called magic methods or special methods, define how objects behave with Python's built-in operations.

Object Lifecycle

class MyClass:
    def __new__(cls, *args, **kwargs):
        # Called to CREATE the instance (before __init__)
        instance = super().__new__(cls)
        return instance

    def __init__(self, value):
        # Called to INITIALIZE the instance
        self.value = value

    def __del__(self):
        # Called when the object is garbage-collected (unreliable — avoid relying on it)
        print("Deleted")

String Representation

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

    def __repr__(self):
        # Unambiguous, developer-facing; used in REPL and repr()
        return f"Point({self.x}, {self.y})"

    def __str__(self):
        # Human-readable; used by print() and str()
        return f"({self.x}, {self.y})"

If __str__ is missing, __repr__ is used as a fallback.

Comparison Operators

from functools import total_ordering

@total_ordering  # generates the remaining comparisons from __eq__ and __lt__
class Version:
    def __init__(self, major, minor):
        self.major, self.minor = major, minor

    def __eq__(self, other):
        return (self.major, self.minor) == (other.major, other.minor)

    def __lt__(self, other):
        return (self.major, self.minor) < (other.major, other.minor)
Method Operator
__eq__ ==
__ne__ !=
__lt__ <
__le__ <=
__gt__ >
__ge__ >=

Arithmetic Operators

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

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

    def __mul__(self, scalar):         # self * scalar
        return Vector(self.x * scalar, self.y * scalar)

    def __rmul__(self, scalar):        # scalar * self (reflected)
        return self.__mul__(scalar)

    def __iadd__(self, other):         # self += other (in-place)
        self.x += other.x
        self.y += other.y
        return self

Container Protocol

class Stack:
    def __init__(self):
        self._data = []

    def __len__(self):              # len(stack)
        return len(self._data)

    def __getitem__(self, index):   # stack[i]
        return self._data[index]

    def __setitem__(self, index, value):  # stack[i] = v
        self._data[index] = value

    def __delitem__(self, index):   # del stack[i]
        del self._data[index]

    def __contains__(self, item):   # item in stack
        return item in self._data

    def __iter__(self):             # for x in stack
        return iter(self._data)

Context Manager Protocol

class ManagedResource:
    def __enter__(self):
        print("Acquire")
        return self   # value bound to `as` variable

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Release")
        return False  # True would suppress exceptions

Callable Objects

class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, x):   # instance can be called like a function
        return x * self.factor

double = Multiplier(2)
double(5)  # 10

Hashing

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

    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

    def __hash__(self):           # needed to use as dict key or in sets
        return hash((self.x, self.y))

If you define __eq__, Python sets __hash__ to None unless you also define it.

Key Interview Points