✨ Python OOP — Magic / Dunder Methods

Introduction 🌟

**Magic Methods**, also known as **Dunder Methods** (because they start and end with double underscores), allow Python classes to behave like built-in types. These methods automatically get triggered on certain operations like printing, adding objects, comparing, indexing, etc.

Note

💡 Example: __init__, __str__, __add__, __len__
💡 Magic methods make custom classes more powerful and Pythonic
💡 They provide operator overloading, object representation, iteration behavior, and more

1. Categories of Magic Methods 🧱

  • 📌 Constructor & Initialization Methods
  • 📌 Representation Methods
  • 📌 Arithmetic & Comparison Operators
  • 📌 Container & Iterable Methods
  • 📌 Attribute Access Methods
  • 📌 Object Lifecycle Methods

2. Constructor: __init__ 🏗️

init_demo.py

class Person:
    def __init__(self, name):
        self.name = name

p = Person("Sathish")

✔ Called automatically when an object is created

3. Object Representation: __str__ & __repr__ 📝

str_repr_demo.py

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

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

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

p = Point(3, 4)
print(p)        # __str__
print(repr(p))  # __repr__

__str__ → user-friendly
__repr__ → developer-friendly

4. Arithmetic Magic Methods ➕➖✖️➗

arithmetic_magic.py

class Number:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return Number(self.value + other.value)

    def __sub__(self, other):
        return Number(self.value - other.value)

n1 = Number(10)
n2 = Number(5)
print((n1 + n2).value)
print((n1 - n2).value)

✔ Enables operator overloading

5. Comparison Magic Methods 🔍

comparison_magic.py

class Student:
    def __init__(self, marks):
        self.marks = marks

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

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

s1 = Student(85)
s2 = Student(90)
print(s1 == s2)
print(s1 < s2)

✔ Lets objects be compared with ==, <, etc.

6. Magic Methods for Built-in Functions 💡

len_contains_getitem.py

class Team:
    def __init__(self, members):
        self.members = members

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

    def __contains__(self, item):
        return item in self.members

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

t = Team(["A", "B", "C"])
print(len(t))
print("A" in t)
print(t[1])

✔ Supports len(), in, indexing, etc.
✔ Makes objects behave like containers

7. Attribute Access Magic Methods 🔧

attribute_magic.py

class Demo:
    def __getattr__(self, name):
        return f"{name} not found"

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

d = Demo()
d.x = 10
print(d.y)

✔ Intercept attribute access & assignment
✔ Useful for validation & debugging

8. Iteration Magic Methods 🔄

iteration_magic.py

class Counter:
    def __init__(self, limit):
        self.limit = limit
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < self.limit:
            num = self.current
            self.current += 1
            return num
        raise StopIteration

c = Counter(3)
for i in c:
    print(i)

✔ Enables objects to act as iterators

9. Object Lifecycle Magic Methods 🧬

lifecycle_magic.py

class Sample:
    def __init__(self):
        print("Object created")

    def __del__(self):
        print("Object destroyed")

s = Sample()
del s

__del__ called when object is deleted

10. Callable Objects: __call__ 📞

call_magic.py

class Greeter:
    def __call__(self, name):
        return f"Hello, {name}"

g = Greeter()
print(g("Sathish"))

✔ Makes an object behave like a function

11. Full Example — Complete Magic Method Class 🎁

full_magic_example.py

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

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

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

    def __len__(self):
        return abs(self.x) + abs(self.y)

v1 = Vector(2, 3)
v2 = Vector(1, 4)

print(v1)
print(v1 + v2)
print(len(v1))

✔ Class behaves like native Python data type

12. Common Magic Methods Reference Table 📘

CategoryMagic MethodUsage
Constructor__init__Initialize object
String__str__, __repr__String representations
Arithmetic__add__, __sub__, __mul__Operator overloading
Comparison__eq__, __lt__, __gt__Comparing objects
Container__len__, __getitem__, __contains__List-like behavior
Iteration__iter__, __next__Iterator behavior
Callable__call__Make object callable
Lifecycle__del__Destruction of object

Best Practices 💡

  • ✔ Use magic methods to make classes intuitive & pythonic
  • ✔ Ensure overloaded operators behave logically
  • ✔ Keep method implementations simple & readable
  • ✔ Avoid overusing magic methods — use only when needed

Conclusion 🎉

>>“Magic methods give your Python classes superpowers — letting them behave like natural, built-in types.” ✨

You now fully understand Magic / Dunder Methods in Python! Want the next topic? Try Method Overloading, Callable Objects, Iterable Protocol, or Advanced OOP. Just tell me! 😊