1. Magic (Dunder) Methods
Magic methods (with double underscores) let you define how objects behave with Python operations.
| Method | Purpose | Example |
|---|---|---|
__init__ |
Object initialization | obj = MyClass() |
__str__ |
String representation | print(obj) |
__add__ |
Addition (+) |
obj1 + obj2 |
__len__ |
Length (len()) |
len(obj) |
Vector Class Example
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2) # Vector(6, 8)
Exercise 1: Fraction Class
Create a Fraction class with:
- Magic methods for
+,-, and* __str__to print as "a/b"- Test with fraction operations
2. Decorators
Basic Decorator
def my_decorator(func):
def wrapper():
print("Before function")
func()
print("After function")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
# Output:
# Before function
# Hello!
# After function
Timing Decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Time: {end-start:.4f}s")
return result
return wrapper
Logging Decorator
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
Decorator with Args
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
Exercise 2: Cache Decorator
Create a decorator that:
- Remembers function results for given arguments
- Returns cached result when same args occur
- Test with a recursive Fibonacci function
3. Generators
Basic Generator
def count_up_to(n):
i = 1
while i <= n:
yield i # Pauses here
i += 1
for num in count_up_to(5):
print(num) # 1 2 3 4 5
Memory Efficiency
# Generates numbers one at a time
def infinite_seq():
n = 0
while True:
yield n
n += 1
Generator Expression
# Similar to list comprehension
squares = (x**2 for x in range(10))
print(sum(squares)) # 285
Pipeline Processing
def read_lines(file):
for line in file:
yield line.strip()
def filter_comments(lines):
for line in lines:
if not line.startswith('#'):
yield line
Exercise 3: Prime Number Generator
Create a generator that:
- Yields prime numbers indefinitely
- Use the Sieve of Eratosthenes algorithm
- Test by printing first 10 primes
4. Practical Applications
Custom Context Managers
from contextlib import contextmanager
@contextmanager
def managed_file(name):
try:
f = open(name, 'w')
yield f
finally:
f.close()
with managed_file('hello.txt') as f:
f.write('Hello, world!')
Chained Decorators
@decorator1
@decorator2
def my_func():
pass
# Equivalent to:
# my_func = decorator1(decorator2(my_func))
Generator Pipelines
def integers():
n = 1
while True:
yield n
n += 1
squares = (x*x for x in integers())
odds = (x for x in squares if x % 2 != 0)