backend / python oop / 01_magic_methods_attributes.md

Magic Methods and Attributes in Python

4 interview angles 3 min read source

Magic Methods and Attributes in Python

Python provides special methods (called magic methods or dunder methods) that customize the behavior of objects. They are surrounded by double underscores (__), like __init__, __new__, and __call__.

Let’s explore the important ones:


__new__

  • Responsible for creating a new instance.
  • Called before __init__.
  • Rarely overridden (except in immutable objects like tuple, str).
class MyClass:
    def __new__(cls):
        print("Creating instance")
        return super().__new__(cls)

    def __init__(self):
        print("Initializing instance")

obj = MyClass()

__init__

  • Initializes the object after it is created.
  • Commonly used to set initial attributes.
class Person:
    def __init__(self, name):
        self.name = name

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

__del__

  • Called when an object is about to be destroyed.
  • Can be used to clean up resources (e.g., files, network connections).
class FileHandler:
    def __del__(self):
        print("Closing file")

fh = FileHandler()
del fh

Note: Relying heavily on __del__ is discouraged because object deletion timing is unpredictable (especially with circular references).


__call__

  • Makes an object callable like a regular function.
class Greeter:
    def __call__(self, name):
        print(f"Hello, {name}!")

greet = Greeter()
greet("Alice")  # Output: Hello, Alice!

__slots__

  • Limits the attributes that instances of a class can have.
  • Saves memory by avoiding the creation of a __dict__ per object.
class Point:
    __slots__ = ('x', 'y')

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

p = Point(1, 2)
p.x  # 1
  • Trying to assign an attribute outside __slots__ will raise an AttributeError.

__dict__

  • A dictionary containing all the writable attributes of an object.
class Car:
    def __init__(self, brand):
        self.brand = brand

c = Car("Toyota")
print(c.__dict__)  # Output: {'brand': 'Toyota'}
  • Used for introspection, dynamic attribute assignment, and debugging.

Summary Table

Magic Method Purpose
__new__ Create a new instance
__init__ Initialize a new object
__del__ Cleanup before destruction
__call__ Make an object callable
__slots__ Optimize memory usage
__dict__ Store object attributes

Mastering magic methods helps in writing more efficient, customizable, and clean Python code!

Let me know if you’d like examples of custom __str__, __repr__, or operator overloading next!

Interview angle

  • __new__ versus __init__?”__new__ allocates and returns the instance; __init__ initialises the already-created one. You override __new__ for immutable types (subclassing tuple or str), singletons, or when returning a different class. If __new__ returns something that isn’t an instance of the class, __init__ never runs.
  • “When would you use __slots__?” — to remove the per-instance __dict__ when you have very many small objects. It cuts memory substantially and speeds attribute access, at the cost of no dynamic attributes and complications with multiple inheritance. Measure before reaching for it.
  • “Why is __del__ unreliable?” — it runs when the refcount hits zero, which may be never (reference cycles) or at interpreter shutdown with a half-torn-down environment. Exceptions inside it are swallowed. Use a context manager or weakref.finalize for deterministic cleanup.
  • __repr__ or __str__?”__repr__ is for developers and should ideally be unambiguous enough to reconstruct the object; __str__ is for users. Define __repr__ always — it’s what you see in logs, tracebacks and the REPL, and the default is useless.