Advanced Techniques and Patterns

The moment I discovered list comprehensions, my Python code became dramatically more concise and readable. What used to take 5 lines could now be done in 1. But list comprehensions were just the beginning - Python has many elegant features that separate beginner code from professional-quality code.

These advanced techniques aren’t just about showing off - they make your code more readable, efficient, and maintainable. Let’s explore the patterns that will elevate your Python skills.

List Comprehensions and Beyond

List comprehensions are Python’s way of creating lists concisely. Once you master them, you’ll use them everywhere:

# Basic list comprehension
squares = [x**2 for x in range(10)]

# With conditions
even_squares = [x**2 for x in range(10) if x % 2 == 0]

# Processing existing data
names = ['alice', 'bob', 'charlie']
capitalized = [name.capitalize() for name in names]

# Nested comprehensions (use sparingly)
matrix = [[i*j for j in range(3)] for i in range(3)]

# Dictionary comprehensions
word_lengths = {word: len(word) for word in names}

# Set comprehensions
unique_lengths = {len(word) for word in names}

Generators - Memory-Efficient Iteration

Generators are one of Python’s most powerful features. They create iterators that generate values on-demand, saving memory:

# Generator function
def fibonacci_generator(n):
    """Generate fibonacci numbers up to n"""
    a, b = 0, 1
    while a < n:
        yield a
        a, b = b, a + b

# Generator expression
squares_gen = (x**2 for x in range(1000000))  # Uses minimal memory

# Using generators
for fib in fibonacci_generator(100):
    print(fib)

# Generators are memory efficient
import sys
list_comp = [x**2 for x in range(1000)]      # Creates full list in memory
gen_exp = (x**2 for x in range(1000))        # Creates generator object

print(f"List size: {sys.getsizeof(list_comp)} bytes")
print(f"Generator size: {sys.getsizeof(gen_exp)} bytes")

Decorators - Enhancing Functions

Decorators let you modify or enhance functions without changing their code:

import time
from functools import wraps

def timing_decorator(func):
    """Decorator to measure function execution time"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
        return result
    return wrapper

def retry_decorator(max_attempts=3):
    """Decorator to retry function on failure"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise e
                    print(f"Attempt {attempt + 1} failed: {e}")
            return None
        return wrapper
    return decorator

# Using decorators
@timing_decorator
@retry_decorator(max_attempts=3)
def unreliable_function():
    """Function that sometimes fails"""
    import random
    if random.random() < 0.7:
        raise Exception("Random failure")
    return "Success!"

Context Managers - Resource Management

Context managers ensure proper resource cleanup using the with statement:

# Built-in context managers
with open('file.txt', 'r') as file:
    content = file.read()
# File automatically closed

# Custom context manager using class
class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        self.connection = None
    
    def __enter__(self):
        print(f"Connecting to {self.db_name}")
        self.connection = f"Connection to {self.db_name}"
        return self.connection
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Closing connection to {self.db_name}")
        self.connection = None

# Custom context manager using contextlib
from contextlib import contextmanager

@contextmanager
def temporary_file(filename):
    """Context manager for temporary files"""
    import os
    try:
        # Setup
        with open(filename, 'w') as f:
            f.write("Temporary content")
        yield filename
    finally:
        # Cleanup
        if os.path.exists(filename):
            os.remove(filename)

# Using context managers
with DatabaseConnection("mydb") as conn:
    print(f"Using {conn}")

with temporary_file("temp.txt") as temp_file:
    print(f"Working with {temp_file}")

Advanced Function Techniques

Python functions have many advanced features:

# Default arguments and keyword arguments
def create_user(name, email, age=None, **kwargs):
    """Create user with flexible arguments"""
    user = {
        'name': name,
        'email': email,
        'age': age
    }
    user.update(kwargs)  # Add any additional keyword arguments
    return user

# Variable arguments
def calculate_average(*numbers):
    """Calculate average of any number of arguments"""
    if not numbers:
        return 0
    return sum(numbers) / len(numbers)

# Lambda functions (anonymous functions)
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))

# Higher-order functions
def apply_operation(numbers, operation):
    """Apply operation to all numbers"""
    return [operation(x) for x in numbers]

def double(x):
    return x * 2

def square(x):
    return x ** 2

# Usage
user = create_user("Alice", "[email protected]", role="admin", active=True)
avg = calculate_average(1, 2, 3, 4, 5)
doubled = apply_operation([1, 2, 3], double)

Object-Oriented Programming Basics

Classes help organize code and model real-world concepts:

class BankAccount:
    """Simple bank account class"""
    
    def __init__(self, account_number, initial_balance=0):
        self.account_number = account_number
        self._balance = initial_balance  # Protected attribute
        self._transactions = []
    
    @property
    def balance(self):
        """Get current balance"""
        return self._balance
    
    def deposit(self, amount):
        """Deposit money"""
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        
        self._balance += amount
        self._transactions.append(f"Deposit: +${amount}")
        return self._balance
    
    def withdraw(self, amount):
        """Withdraw money"""
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive")
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        
        self._balance -= amount
        self._transactions.append(f"Withdrawal: -${amount}")
        return self._balance
    
    def get_statement(self):
        """Get account statement"""
        statement = f"Account: {self.account_number}\n"
        statement += f"Balance: ${self._balance}\n"
        statement += "Recent transactions:\n"
        for transaction in self._transactions[-5:]:  # Last 5 transactions
            statement += f"  {transaction}\n"
        return statement
    
    def __str__(self):
        return f"BankAccount({self.account_number}, ${self._balance})"

# Usage
account = BankAccount("12345", 1000)
account.deposit(500)
account.withdraw(200)
print(account.get_statement())

Error Handling Patterns

Proper error handling makes your code robust:

class ValidationError(Exception):
    """Custom exception for validation errors"""
    pass

def validate_email(email):
    """Validate email format"""
    if '@' not in email:
        raise ValidationError("Email must contain @ symbol")
    if '.' not in email.split('@')[1]:
        raise ValidationError("Email domain must contain a dot")
    return True

def safe_divide(a, b):
    """Safely divide two numbers"""
    try:
        result = a / b
        return result, None
    except ZeroDivisionError:
        return None, "Cannot divide by zero"
    except TypeError:
        return None, "Arguments must be numbers"

def process_user_data(data):
    """Process user data with comprehensive error handling"""
    try:
        # Validate required fields
        if 'email' not in data:
            raise ValidationError("Email is required")
        
        validate_email(data['email'])
        
        # Process data
        processed = {
            'email': data['email'].lower().strip(),
            'name': data.get('name', '').strip(),
            'age': int(data.get('age', 0))
        }
        
        return processed, None
        
    except ValidationError as e:
        return None, f"Validation error: {e}"
    except ValueError as e:
        return None, f"Value error: {e}"
    except Exception as e:
        return None, f"Unexpected error: {e}"

# Usage with error handling
user_data = {'email': '[email protected]', 'name': 'Alice', 'age': '30'}
result, error = process_user_data(user_data)

if error:
    print(f"Error: {error}")
else:
    print(f"Processed: {result}")

Working with Files and Data

Advanced file handling patterns:

import json
import csv
from pathlib import Path

class DataManager:
    """Manage different data formats"""
    
    def __init__(self, data_dir):
        self.data_dir = Path(data_dir)
        self.data_dir.mkdir(exist_ok=True)
    
    def save_json(self, filename, data):
        """Save data as JSON"""
        file_path = self.data_dir / f"{filename}.json"
        with open(file_path, 'w') as f:
            json.dump(data, f, indent=2)
    
    def load_json(self, filename):
        """Load data from JSON"""
        file_path = self.data_dir / f"{filename}.json"
        try:
            with open(file_path, 'r') as f:
                return json.load(f)
        except FileNotFoundError:
            return None
    
    def save_csv(self, filename, data, headers):
        """Save data as CSV"""
        file_path = self.data_dir / f"{filename}.csv"
        with open(file_path, 'w', newline='') as f:
            writer = csv.DictWriter(f, fieldnames=headers)
            writer.writeheader()
            writer.writerows(data)
    
    def load_csv(self, filename):
        """Load data from CSV"""
        file_path = self.data_dir / f"{filename}.csv"
        try:
            with open(file_path, 'r') as f:
                return list(csv.DictReader(f))
        except FileNotFoundError:
            return []

# Usage
dm = DataManager("data")

# Save and load JSON
user_data = {'name': 'Alice', 'age': 30, 'email': '[email protected]'}
dm.save_json('user', user_data)
loaded_user = dm.load_json('user')

# Save and load CSV
users = [
    {'name': 'Alice', 'age': 30, 'email': '[email protected]'},
    {'name': 'Bob', 'age': 25, 'email': '[email protected]'}
]
dm.save_csv('users', users, ['name', 'age', 'email'])
loaded_users = dm.load_csv('users')

Functional Programming Concepts

Python supports functional programming patterns:

from functools import reduce, partial

# Map, filter, reduce
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Map - transform each element
squared = list(map(lambda x: x**2, numbers))

# Filter - select elements that meet criteria
evens = list(filter(lambda x: x % 2 == 0, numbers))

# Reduce - combine elements into single value
sum_all = reduce(lambda x, y: x + y, numbers)
product = reduce(lambda x, y: x * y, numbers)

# Partial functions
def multiply(x, y):
    return x * y

double = partial(multiply, 2)  # Fix first argument to 2
triple = partial(multiply, 3)  # Fix first argument to 3

# Usage
print(double(5))  # 10
print(triple(4))  # 12

# Function composition
def compose(f, g):
    """Compose two functions"""
    return lambda x: f(g(x))

def add_one(x):
    return x + 1

def square(x):
    return x ** 2

# Compose functions
add_then_square = compose(square, add_one)
print(add_then_square(3))  # (3 + 1)^2 = 16

These advanced techniques transform how you write Python code. They’re not just syntactic sugar - they represent different ways of thinking about problems and solutions. Master these patterns, and your code will become more elegant, efficient, and maintainable.

Next, we’ll explore best practices and optimization techniques that ensure your Python code is not just functional, but professional-quality.