Skip to content

Magic Methods

Magic methods (a.k.a. โ€œdunderโ€ methods, for their double-underscore names like __init__) are special hooks Python calls automatically for built-in operations: constructing an object, printing it, comparing it, adding it, calling it like a function. Defining one on your class opts that class into the corresponding built-in behavior.

Without magic methods, a class can only expose behavior through explicitly named methods โ€” nothing integrates with Pythonโ€™s own syntax:

class Money:
def __init__(self, amount):
self.amount = amount
def to_string(self):
return f"${self.amount}"
def add(self, other):
return Money(self.amount + other.amount)
m = Money(10)
print(m.to_string()) # have to remember the method name
total = m.add(Money(5)) # can't just write m + Money(5)
class Money:
def __init__(self, amount):
self.amount = amount
def __repr__(self):
return f"Money({self.amount!r})" # unambiguous, for developers/debugging
def __str__(self):
return f"${self.amount:.2f}" # readable, for end users
def __add__(self, other):
return Money(self.amount + other.amount)
def __eq__(self, other):
return self.amount == other.amount
def __call__(self, multiplier):
return Money(self.amount * multiplier)
m = Money(10)
print(m) # ${10.00} -> uses __str__
repr(m) # 'Money(10)' -> uses __repr__
m + Money(5) # Money(15) -> uses __add__
m == Money(10) # True -> uses __eq__
m(3) # Money(30) -> uses __call__, m behaves like a function
  • Integrates with the language: print(), +, ==, len(), the debugger, and f-strings all โ€œjust workโ€ on your objects.
  • __repr__ vs __str__ split intent: __repr__ is for developers (ideally eval-able or at least unambiguous); __str__ is for end users. If only __repr__ is defined, Python falls back to it for str() too.
  • __call__ enables callable objects: a class instance can act like a function while still holding state between calls โ€” useful for configurable callbacks, decorators-as-classes, and memoizing wrappers.
  • __init__ initializes an already-created instance (self exists); __new__ is what actually creates the instance and must return it โ€” you rarely override __new__ except for immutable types (subclassing str/tuple) or singleton patterns.
  • If you define __eq__, Python sets __hash__ to None unless you define it too โ€” your objects become unhashable (canโ€™t go in a set or be a dict key) unless you explicitly re-add __hash__.
  • print(obj) uses __str__; if __str__ is missing, Python falls back to __repr__. The reverse is not true.
  • object.__new__(cls) is called before __init__; overriding __new__ is how immutable-type subclasses set their value, since by the time __init__ runs the (immutable) object already exists.
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
a, b = Singleton(), Singleton()
print(a is b) # True โ€” __new__ returned the same object both times
  1. Whatโ€™s the difference between __str__ and __repr__?

    Answer`__str__` is for a readable, user-facing string (used by `print()`/`str()`); `__repr__` is for an unambiguous, developer-facing string (used by `repr()` and the interactive console), and is the fallback if `__str__` isn't defined.
  2. Why would you override __new__ instead of __init__?

    Answer`__new__` controls object *creation* and must run before `__init__` โ€” you need it to subclass an immutable type (where the value has to be set at creation time) or to implement patterns like singletons that decide whether to create a new instance at all.
  3. What does defining __call__ on a class let you do?

    AnswerCall an instance like a function โ€” `instance(args)` โ€” while the instance still holds its own state between calls, unlike a plain function.