Python
Python Basics
- Introduction to Python and Its History
- Python Syntax and Indentation
- Python Variables and Data Types
- Dynamic and Strong Typing
- Comments and Docstrings
- Taking User Input (input())
- Printing Output (print())
- Python Operators (Arithmetic, Logical, Comparison)
- Type Conversion and Casting
- Escape Characters and Raw Strings
Data Structures in Python
- Lists
- Dictionaries
- Dictionary Comprehensions
- Strings and String Manipulation
- Tuples
- Python Sets: Unordered Collections
- List Comprehensions and Generator Expressions
- Set Comprehensions
- String Formatting
- Indexing and Slicing
Control Flow and Loops
- Conditional Statements: if, elif, and else
- Loops and Iteration
- While Loops
- Nested Loops
- Loop Control Statements
- Iterators and Iterables
- List, Dictionary, and Set Iterations
Functions and Scope
- Defining and Calling Functions (`def`)
- Function Arguments (`*args`, `**kwargs`)
- Default Arguments and Keyword Arguments
- Lambda Functions
- Global and Local Scope
- Function Return Values
- Recursion in Python
Object-Oriented Programming (OOP)
- Object-Oriented Programming
- Classes and Objects
- the `__init__()` Constructor
- Instance Variables and Methods
- Class Variables and `@classmethod`
- Encapsulation and Data Hiding
- Inheritance and Subclasses
- Method Overriding and super()
- Polymorphism
- Magic Methods and Operator Overloading
- Static Methods
- Abstract Classes and Interfaces
Python Programs
- Array : Find median in an integer array
- Array : Find middle element in an integer array
- Array : Find out the duplicate in an array
- Array : Find print all subsets in an integer array
- Program : Array : Finding missing number between from 1 to n
- Array : Gap and Island problem
- Python Program stock max profit
- Reverse words in Python
- Python array duplicate program
- Coin change problem in python
- Python Write fibonacci series program
- Array : find all the pairs whose sum is equal to a given number
- Find smallest and largest number in array
- Iterate collections
- List comprehensions
- Program: Calculate Pi in Python
- String Formatting in Python
Understanding Magic Methods and Operator Overloading in Python: A Beginner’s Hands-On Guide
When you first dive into Python and start exploring object-oriented programming (OOP), you’ll come across something curious: method names that start and end with double underscores — like __init__
, __str__
, __repr__
, and __add__
. These are called magic methods or dunder methods (short for “double underscore”).
Although they might seem a bit mysterious at first, these special methods are one of the most elegant and powerful features in Python. They allow you to write cleaner, more intuitive code, especially when customizing how your objects behave.
In this article, we’ll break down what magic methods are, and focus specifically on three of the most useful ones:
__str__()
__repr__()
__add__()
Let’s walk through what they do and how you can use them to make your own Python classes smarter.
What Are Magic Methods in Python?
Magic methods are special methods predefined by Python. They let you define how your objects interact with built-in Python syntax and functions. These methods are always surrounded by double underscores.
Here are some common uses:
- Creating objects:
__init__()
- Printing:
__str__()
,__repr__()
- Arithmetic:
__add__()
,__sub__()
, etc. - Comparisons:
__eq__()
,__lt__()
, etc. - Length:
__len__()
They’re not meant to be called directly. Instead, Python automatically invokes them based on context.
Why Use Magic Methods?
Magic methods allow you to:
- Customize how your objects are represented.
- Define how objects behave with operators (like
+
,-
,*
). - Make your custom classes feel like native Python types.
1. __str__()
— The Human-Friendly String Representation
The __str__()
method defines what should be returned when you use print()
on an object.
Example:
class Book: def __init__(self, title, author): self.title = title self.author = author
def __str__(self): return f"{self.title} by {self.author}"
book = Book("1984", "George Orwell")print(book)
Output:
1984 by George Orwell
Without __str__()
, printing the object would show something like <__main__.Book object at 0x...>
, which isn’t helpful.
✅ Use
__str__()
when you want a readable, user-facing string.
2. __repr__()
— The Developer-Friendly String Representation
The __repr__()
method is used mostly for debugging. It should return a string that, ideally, could recreate the object when passed to eval()
.
Example:
class Book: def __init__(self, title, author): self.title = title self.author = author
def __repr__(self): return f"Book('{self.title}', '{self.author}')"
book = Book("The Alchemist", "Paulo Coelho")print(repr(book))
Output:
Book('The Alchemist', 'Paulo Coelho')
✅ Use
__repr__()
for a detailed and precise string for developers.
Bonus Tip: If you only define __repr__()
and not __str__()
, Python will use __repr__()
as a fallback when calling print()
.
3. __add__()
— Operator Overloading for +
By default, the +
operator works with numbers and strings. But what if you want to use it on custom objects?
That’s where __add__()
comes in. You can override it to define how the +
operator should behave for your class.
Example:
class Point: def __init__(self, x, y): self.x = x self.y = y
def __add__(self, other): return Point(self.x + other.x, self.y + other.y)
def __repr__(self): return f"Point({self.x}, {self.y})"
p1 = Point(2, 3)p2 = Point(4, 5)result = p1 + p2print(result)
Output:
Point(6, 8)
Here, the +
operator creates a new Point
object by adding the x
and y
values of two existing points.
✅ Use
__add__()
to enable intuitive addition between your custom objects.
Putting It All Together
Let’s build a more complete example to reinforce how these methods work together.
Example: A Custom Money Class
class Money: def __init__(self, amount, currency="USD"): self.amount = amount self.currency = currency
def __add__(self, other): if self.currency != other.currency: raise ValueError("Cannot add different currencies") return Money(self.amount + other.amount, self.currency)
def __str__(self): return f"{self.amount} {self.currency}"
def __repr__(self): return f"Money({self.amount}, '{self.currency}')"
wallet1 = Money(50)wallet2 = Money(75)wallet3 = wallet1 + wallet2
print(wallet3) # Output: 125 USDprint(repr(wallet3)) # Output: Money(125, 'USD')
This custom class now behaves like a built-in type:
- You can add two
Money
objects using+
. - You get readable output with
print()
. - You get debug-friendly output with
repr()
.
More Magic Methods to Explore
Once you’re comfortable with __str__
, __repr__
, and __add__
, here are a few more worth exploring:
__eq__()
— for==
comparisons__lt__()
— for<
comparisons__len__()
— to define behavior forlen(obj)
__getitem__()
— to allow indexing (obj[i]
)__call__()
— to make an object callable like a function
Tips and Best Practices
- Use
__str__()
for readable output and__repr__()
for debugging. - Always provide fallback behavior to handle unexpected input in operator methods like
__add__()
. - Keep magic method implementations simple and consistent.
- Don’t overuse operator overloading — only use it when it makes code cleaner and more natural.
Magic methods in Python are not just syntactic sugar — they are the secret sauce that makes Python so expressive and powerful. By customizing methods like __str__
, __repr__
, and __add__
, you can make your custom classes behave like native Python objects.
This not only improves readability but also leads to cleaner and more maintainable code. Whether you’re building data structures, games, or web applications, understanding and using magic methods will take your Python skills to the next level.
So, next time you see a __method__
in Python, don’t be intimidated. Embrace the magic!