Module 12 - Special Methods (Dunder Methods & Operator Overloading)
This module covers special methods (dunder methods) in Python. You'll learn how to customize object behavior, overload operators, and implement magic methods for rich functionality.
1. Introduction to Special Methods
1.1 What are Special Methods?
Special methods (also called magic methods or dunder methods) start and end with double underscores (__). They allow you to customize how objects behave with built-in operations.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2 # Calls __add__
print(p3) # Calls __str__: Point(4, 6)
2. Object Initialization and Representation
2.1 Initialization Methods
class Person:
def __init__(self, name, age):
"""Constructor - called when creating instance"""
self.name = name
self.age = age
def __del__(self):
"""Destructor - called when object is garbage collected"""
print(f"{self.name} is being deleted")
person = Person("Alice", 25)
del person # Triggers __del__
2.2 String Representation
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
"""User-friendly string"""
return f'"{self.title}" by {self.author}'
def __repr__(self):
"""Developer-friendly representation"""
return f'Book("{self.title}", "{self.author}")'
def __format__(self, format_spec):
"""Custom formatting"""
if format_spec == 'short':
return self.title
return str(self)
book = Book("1984", "George Orwell")
print(str(book)) # "1984" by George Orwell
print(repr(book)) # Book("1984", "George Orwell")
print(f"{book:short}") # 1984
3. Arithmetic Operators
3.1 Basic Arithmetic
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
"""Addition: v1 + v2"""
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
"""Subtraction: v1 - v2"""
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
"""Scalar multiplication: v * 2"""
return Vector(self.x * scalar, self.y * scalar)
def __truediv__(self, scalar):
"""Division: v / 2"""
return Vector(self.x / scalar, self.y / scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(v1 - v2) # Vector(2, 2)
print(v1 * 2) # Vector(6, 8)
print(v1 / 2) # Vector(1.5, 2.0)
3.2 Augmented Assignment
class Counter:
def __init__(self, value=0):
self.value = value
def __iadd__(self, other):
"""In-place addition: c += 5"""
self.value += other
return self
def __isub__(self, other):
"""In-place subtraction: c -= 3"""
self.value -= other
return self
def __str__(self):
return str(self.value)
counter = Counter(10)
counter += 5
print(counter) # 15
counter -= 3
print(counter) # 12
4. Comparison Operators
4.1 Rich Comparison Methods
class Money:
def __init__(self, amount):
self.amount = amount
def __eq__(self, other):
"""Equal: =="""
return self.amount == other.amount
def __ne__(self, other):
"""Not equal: !="""
return self.amount != other.amount
def __lt__(self, other):
"""Less than: <"""
return self.amount < other.amount
def __le__(self, other):
"""Less than or equal: <="""
return self.amount <= other.amount
def __gt__(self, other):
"""Greater than: >"""
return self.amount > other.amount
def __ge__(self, other):
"""Greater than or equal: >="""
return self.amount >= other.amount
def __str__(self):
return f"${self.amount}"
m1 = Money(100)
m2 = Money(200)
print(m1 == m2) # False
print(m1 < m2) # True
print(m1 <= m2) # True
5. Container Methods
5.1 Sequence Protocol
class CustomList:
def __init__(self, items=None):
self.items = items or []
def __len__(self):
"""Length: len(obj)"""
return len(self.items)
def __getitem__(self, index):
"""Get item: obj[index]"""
return self.items[index]
def __setitem__(self, index, value):
"""Set item: obj[index] = value"""
self.items[index] = value
def __delitem__(self, index):
"""Delete item: del obj[index]"""
del self.items[index]
def __contains__(self, item):
"""Membership: item in obj"""
return item in self.items
def __iter__(self):
"""Iteration: for item in obj"""
return iter(self.items)
clist = CustomList([1, 2, 3, 4, 5])
print(len(clist)) # 5
print(clist[2]) # 3
print(3 in clist) # True
clist[0] = 10
print(clist[0]) # 10
for item in clist:
print(item, end=' ') # 10 2 3 4 5
5.2 Custom Dictionary
class CaseInsensitiveDict:
def __init__(self):
self._data = {}
def __setitem__(self, key, value):
self._data[key.lower()] = value
def __getitem__(self, key):
return self._data[key.lower()]
def __delitem__(self, key):
del self._data[key.lower()]
def __contains__(self, key):
return key.lower() in self._data
def __len__(self):
return len(self._data)
d = CaseInsensitiveDict()
d["Name"] = "Alice"
d["AGE"] = 25
print(d["name"]) # Alice
print(d["Age"]) # 25
print("NAME" in d) # True
6. Callable Objects
6.1 Making Objects Callable
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
"""Makes instance callable like a function"""
return x * self.factor
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# Check if callable
print(callable(double)) # True
6.2 Function-like Classes
class Adder:
def __init__(self, start=0):
self.total = start
def __call__(self, value):
self.total += value
return self.total
adder = Adder(10)
print(adder(5)) # 15
print(adder(3)) # 18
print(adder(7)) # 25
7. Context Managers
7.1 Implementing enter and exit
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
"""Called when entering 'with' block"""
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""Called when exiting 'with' block"""
if self.file:
self.file.close()
# Return False to propagate exceptions
return False
with FileManager('test.txt', 'w') as f:
f.write('Hello, World!')
# File automatically closed
7.2 Database Connection Manager
class DatabaseConnection:
def __init__(self, connection_string):
self.connection_string = connection_string
self.connection = None
def __enter__(self):
print(f"Connecting to {self.connection_string}")
self.connection = f"Connection to {self.connection_string}"
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing connection")
self.connection = None
return False
with DatabaseConnection("localhost:5432") as conn:
print(f"Using {conn}")
# Do database operations
# Connection automatically closed
8. Attribute Access
8.1 Controlling Attribute Access
class DynamicAttributes:
def __init__(self):
self._data = {}
def __getattr__(self, name):
"""Called when attribute not found"""
return self._data.get(name, f"No attribute '{name}'")
def __setattr__(self, name, value):
"""Called when setting attribute"""
if name == '_data':
super().__setattr__(name, value)
else:
self._data[name] = value
def __delattr__(self, name):
"""Called when deleting attribute"""
if name in self._data:
del self._data[name]
obj = DynamicAttributes()
obj.name = "Alice"
obj.age = 25
print(obj.name) # Alice
print(obj.nonexistent) # No attribute 'nonexistent'
del obj.name
print(obj.name) # No attribute 'name'
9. Numeric Conversions
9.1 Type Conversion Methods
class Percentage:
def __init__(self, value):
self.value = value
def __int__(self):
"""Convert to int: int(obj)"""
return int(self.value)
def __float__(self):
"""Convert to float: float(obj)"""
return float(self.value)
def __str__(self):
return f"{self.value}%"
def __bool__(self):
"""Convert to bool: bool(obj)"""
return self.value > 0
p = Percentage(75.5)
print(int(p)) # 75
print(float(p)) # 75.5
print(bool(p)) # True
10. Summary
| Method | Operation | Example |
|---|---|---|
__init__ | Constructor | obj = Class() |
__str__ | String representation | str(obj) |
__repr__ | Developer representation | repr(obj) |
__add__ | Addition | obj1 + obj2 |
__sub__ | Subtraction | obj1 - obj2 |
__mul__ | Multiplication | obj * 2 |
__eq__ | Equality | obj1 == obj2 |
__lt__ | Less than | obj1 < obj2 |
__len__ | Length | len(obj) |
__getitem__ | Indexing | obj[key] |
__setitem__ | Assignment | obj[key] = value |
__call__ | Call as function | obj() |
__enter__/__exit__ | Context manager | with obj: |
- Special methods customize object behavior
- Enable operator overloading
- Make objects behave like built-in types
- Follow Python conventions
- Implement only needed methods
11. What's Next?
In Module 13 - Decorators & Closures, you'll learn:
- Function decorators
- Class decorators
- Closures and scope
- Built-in decorators
- Creating custom decorators
12. Practice Exercises
Exercise 1: Complex Number Class
Implement a complex number class with arithmetic operations.
Exercise 2: Custom Range
Create a custom range class that mimics Python's range().
Exercise 3: Matrix Class
Build a matrix class with addition, multiplication, and indexing.
Exercise 4: Smart List
Create an enhanced list with additional methods and operator overloading.
Exercise 5: Timer Context Manager
Implement a context manager that times code execution.
Try solving these exercises on your own first. Solutions will be provided in the practice section.