What Are Dunder Methods?
CheatsheetDefinition
Dunder methods (short for "double underscore") are special methods in Python that have double underscores at the beginning and end of their names. They're also called "magic methods" or "special methods."
Why "Dunder"? It's a contraction of "Double UNDERscore" - __init__ is pronounced "dunder init".
Key Characteristics
- Automatically invoked by Python in specific situations
- Enable operator overloading (+, -, *, /, etc.)
- Make custom classes behave like built-in types
- Implement context managers, iteration, and more
Basic Dunder Method Example
class Student:
def __init__(self, name, age):
# __init__ is called when creating a new instance
self.name = name
self.age = age
def __str__(self):
# __str__ provides a readable string representation
return f"Student: {self.name}, Age: {self.age}"
def __repr__(self):
# __repr__ provides an official string representation
return f"Student('{self.name}', {self.age})"
# Usage
student = Student("Alice", 21)
print(student) # Calls __str__: "Student: Alice, Age: 21"
print(repr(student)) # Calls __repr__: "Student('Alice', 21)"
Dunder Method Categories
Initialization & Construction
Methods for object creation and initialization
__init____new____del__
String Representation
Methods for converting objects to strings
__str____repr____format__
Arithmetic Operations
Methods for mathematical operations
__add____sub____mul__
Comparison Operations
Methods for comparing objects
__eq____lt____gt__
Common Dunder Methods with W3Schools References
| Category | Method | Purpose | W3Schools Reference |
|---|---|---|---|
| Initialization |
__init__
|
Object constructor, initializes new instances | View |
| Initialization |
__new__
|
Creates and returns a new instance (called before __init__) | View |
| String |
__str__
|
Returns readable string representation for users | View |
| String |
__repr__
|
Returns official string representation for developers | View |
| Arithmetic |
__add__
|
Implements addition (+) operator | View |
| Arithmetic |
__sub__
|
Implements subtraction (-) operator | View |
| Comparison |
__eq__
|
Implements equality (==) operator | View |
| Comparison |
__lt__
|
Implements less than (<) operator | View |
__init__
Constructor Method
Purpose
The __init__ method is the constructor of a class. It's automatically called when
a new instance of the class is created. This method initializes the object's attributes.
When Called
- When creating a new object:
obj = MyClass() - Before any other method is called on the new instance
- Only once per object creation
Example Code
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
self.current_page = 1
def read(self):
print(f"Reading {self.title} by {self.author}")
# __init__ is called automatically here
book1 = Book("Python Basics", "John Doe", 300)
book2 = Book("Advanced Python", "Jane Smith", 500)
print(book1.title) # Output: Python Basics
print(book2.pages) # Output: 500
Use Cases
- Setting initial values for object attributes
- Validating input parameters
- Establishing connections (database, network)
- Loading configuration or initial data
__str__
String Representation Methods
__repr__
__str__ vs __repr__
__str__ (For Users)
Should return a readable, nicely formatted string intended for end users.
Called by str() and print() functions.
__repr__ (For Developers)
Should return an unambiguous, official string representation that could be used
to recreate the object. Called by repr() function.
Golden Rule
__repr__ is for developers, __str__ is for users. If you only implement one, implement __repr__ (__str__ will fall back to __repr__).
Example Code
class Product:
def __init__(self, name, price, category):
self.name = name
self.price = price
self.category = category
def __str__(self):
# User-friendly representation
return f"{self.name} - ${self.price} ({self.category})"
def __repr__(self):
# Developer representation (can recreate object)
return f"Product('{self.name}', {self.price}, '{self.category}')"
# Create product instance
product = Product("Laptop", 999.99, "Electronics")
# Different outputs
print(str(product)) # Output: Laptop - $999.99 (Electronics)
print(repr(product)) # Output: Product('Laptop', 999.99, 'Electronics')
print(product) # Calls __str__: Laptop - $999.99 (Electronics)
Practical Tips
- Always implement
__repr__for debugging purposes __str__should be readable and user-friendly__repr__should ideally allow object recreation witheval()- In interactive Python shell, objects show their
__repr__
__add__
Addition Operator Overloading
Purpose
The __add__ method allows you to define what the + operator
does when used with instances of your class. This is called operator overloading.
How It Works
When Python encounters obj1 + obj2, it calls obj1.__add__(obj2).
The method should return a new object representing the result of the addition.
Related Methods
__sub__
__mul__
__truediv__
__floordiv__
__mod__
Example Code
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# Define vector addition
if isinstance(other, Vector):
# Add corresponding components
return Vector(self.x + other.x, self.y + other.y)
elif isinstance(other, (int, float)):
# Scalar addition (add to both components)
return Vector(self.x + other, self.y + other)
else:
raise TypeError("Can only add Vector or number to Vector")
def __str__(self):
return f"Vector({self.x}, {self.y})"
# Using the + operator with Vector objects
v1 = Vector(2, 3)
v2 = Vector(4, 5)
# Vector addition
v3 = v1 + v2
print(v3) # Output: Vector(6, 8)
# Scalar addition
v4 = v1 + 10
print(v4) # Output: Vector(12, 13)
# This would raise TypeError
# v5 = v1 + "hello"
Common Use Cases
- Mathematical objects (vectors, matrices, complex numbers)
- Money/currency objects (adding amounts)
- Collection objects (combining lists, sets)
- Time/duration objects
- Custom data structures
Comprehensive Example: Bank Account Class
Real-World Implementation
A BankAccount class using multiple dunder methods
class BankAccount:
def __init__(self, account_holder, balance=0):
# Initialize account with holder name and balance
self.account_holder = account_holder
self.balance = balance
self.transaction_history = []
def __str__(self):
# User-friendly string representation
return f"Bank Account: {self.account_holder}, Balance: ${self.balance:.2f}"
def __repr__(self):
# Developer representation
return f"BankAccount('{self.account_holder}', {self.balance})"
def __add__(self, other):
# Combine two accounts (e.g., joint account)
if isinstance(other, BankAccount):
new_account = BankAccount(
f"{self.account_holder} & {other.account_holder}",
self.balance + other.balance
)
return new_account
elif isinstance(other, (int, float)):
# Deposit money
return BankAccount(self.account_holder, self.balance + other)
else:
raise TypeError("Can only add BankAccount or number")
def __eq__(self, other):
# Compare accounts by balance
if isinstance(other, BankAccount):
return self.balance == other.balance
return False
def __lt__(self, other):
# Compare if balance is less than another account
if isinstance(other, BankAccount):
return self.balance < other.balance
return NotImplemented
def __len__(self):
# Return number of transactions
return len(self.transaction_history)
def __contains__(self, transaction):
# Check if transaction is in history
return transaction in self.transaction_history
def deposit(self, amount):
self.balance += amount
self.transaction_history.append(f"Deposit: +${amount}")
return f"Deposited ${amount}"
def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
self.transaction_history.append(f"Withdrawal: -${amount}")
return f"Withdrew ${amount}"
else:
return "Insufficient funds"
# Demonstration of all dunder methods
account1 = BankAccount("Alice", 1000)
account2 = BankAccount("Bob", 2000)
print(str(account1)) # Calls __str__
print(repr(account2)) # Calls __repr__
# Using operators
joint_account = account1 + account2 # Calls __add__
print(str(joint_account)) # Bank Account: Alice & Bob, Balance: $3000.00
print(account1 == account2) # Calls __eq__, False
print(account1 < account2) # Calls __lt__, True
# Using other dunder methods
account1.deposit(500)
account1.withdraw(200)
print(len(account1)) # Calls __len__, 2
print("Deposit: +$500" in account1) # Calls __contains__, True
Initialization
__init__ sets up account holder and initial balance
String Representation
__str__ and __repr__ provide different string formats
Operator Overloading
__add__, __eq__, __lt__ enable intuitive operations