Chapter 11: Advanced Python – Magic Methods, Decorators & Generators

Previous Chapter Table of Contents Next Chapter

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:

  1. Magic methods for +, -, and *
  2. __str__ to print as "a/b"
  3. 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:

  1. Remembers function results for given arguments
  2. Returns cached result when same args occur
  3. 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:

  1. Yields prime numbers indefinitely
  2. Use the Sieve of Eratosthenes algorithm
  3. 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)
Previous Chapter Next Chapter