Magic methods (also known as dunder methods, for “double underscore”) are special methods in Python that enable operator overloading and hook into built-in behaviors.

🔹 __init__: Object Constructor
Called when an object is created.
class Dog:
    def __init__(self, name):
        self.name = name
dog = Dog("Fido")
print(dog.name)  # Fido🔹 __str__ and __repr__: String Representations
class Dog:
    def __init__(self, name): self.name = name
    def __str__(self): return f"Dog named {self.name}"
    def __repr__(self): return f"Dog({self.name!r})"
dog = Dog("Fido")
print(dog)        # Dog named Fido
print(repr(dog))  # Dog('Fido')🔹 __len__: Length of Object
Used by len(obj)
class Basket:
    def __init__(self, items): self.items = items
    def __len__(self): return len(self.items)
b = Basket(["apple", "banana"])
print(len(b))  # 2🔹 __getitem__, __setitem__, __delitem__: Indexing
class Container:
    def __init__(self): self.data = {}
    def __getitem__(self, key): return self.data[key]
    def __setitem__(self, key, value): self.data[key] = value
    def __delitem__(self, key): del self.data[key]
c = Container()
c['x'] = 42
print(c['x'])  # 42
del c['x']🔹 __call__: Makes Object Callable Like a Function
class Greeter:
    def __call__(self, name): return f"Hello, {name}!"
greet = Greeter()
print(greet("Alice"))  # Hello, Alice!🔹 __eq__, __lt__, etc.: Comparisons
class Point:
    def __init__(self, x): self.x = x
    def __eq__(self, other): return self.x == other.x
    def __lt__(self, other): return self.x < other.x
a = Point(1)
b = Point(2)
print(a == b)  # False
print(a < b)   # True🔹 __add__, __sub__, __mul__, etc.: Operator Overloading
class Vector:
    def __init__(self, x): self.x = x
    def __add__(self, other): return Vector(self.x + other.x)
    def __repr__(self): return f"Vector({self.x})"
v1 = Vector(3)
v2 = Vector(4)
print(v1 + v2)  # Vector(7)🔹 __enter__ and __exit__: Context Manager (with)
class FileManager:
    def __enter__(self):
        print("Open resource")
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Close resource")
with FileManager():
    print("Using resource")
# Output:
# Open resource
# Using resource
# Close resource🔹 __getattr__ and __setattr__: Dynamic Attribute Access
class Dynamic:
    def __getattr__(self, name): return f"No attribute '{name}'"
    def __setattr__(self, name, value):
        print(f"Setting {name} = {value}")
        super().__setattr__(name, value)
d = Dynamic()
print(d.foo)  # No attribute 'foo'
d.x = 10      # Setting x = 10
🔹 __del__: Object Destructor (⚠️ rarely needed)
class Temp:
    def __del__(self):
        print("Object destroyed")
t = Temp()
del t  # Object destroyed (maybe)🔹 __new__: Call the constructor (⚠️ rarely needed)
__new__ is called before __init__ and is responsible for actually creating the instance of the class. It’s most useful when:
- You’re subclassing immutable types (int,str,tuple)
- You need to control the instantiation process (e.g., singleton pattern)
Subclassing immutable types:
class MyInt(int):
    def __new__(cls, value):
        return super().__new__(cls, value + 1)
x = MyInt(5)
print(x)  # 6Singleton design pattern:
class Singleton:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
a = Singleton()
b = Singleton()
print(a is b)  # True__new__ vs __init__
| _new__ | __init__ | 
| Creates the object (allocates memory) | Initializes the object after it’s created | 
| First method called during instantiation | Second method called | 
| Used for immutable types and special creation | Used for setting attributes | 
Cheatsheet of magic methods
| Purpose | Method | 
| Constructor | __new__, __init__ | 
| String display | __str__,__repr__ | 
| Collections | __iter__,__next__ | 
| Operators | __add__,__eq__, etc. | 
| Iteration | __iter__,__next__ | 
| Context manager | __enter__,__exit__ | 
| Attribute access | __getattr__,__setattr__ | 

Early in my career, I specialized in the Python language. Python has been a constant in my professional life for over 10 years now. In 2018, I moved to London where I worked at companies of various sizes as a Python developer for five years. In parallel, I developed my activity as a Mentor, to which I now dedicate myself full-time.