Back to Blog
Advanced Python: Decorators Explained
Advanced
18 min read
November 2, 2024

Advanced Python: Decorators Explained

Alex Thompson

Alex Thompson

Python Instructor

Advanced Python: Decorators Explained

Decorators are one of Python's most powerful features, allowing you to modify or enhance functions and classes without changing their code directly. Let's dive deep into how they work.

What Are Decorators?

A decorator is a function that takes another function as input and extends or modifies its behavior without explicitly changing it.

Simple Decorator Example

def my_decorator(func): def wrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper @my_decorator def say_hello(): print("Hello!") say_hello() # Output: # Something is happening before the function is called. # Hello! # Something is happening after the function is called.

Decorators with Arguments

def timer_decorator(func): import time 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 @timer_decorator def slow_function(): import time time.sleep(1) return "Done!" result = slow_function() # Output: slow_function took 1.0001 seconds

Practical Use Cases

1. Authentication Decorator

def require_auth(func): def wrapper(*args, **kwargs): if not user_is_authenticated(): raise PermissionError("Authentication required") return func(*args, **kwargs) return wrapper @require_auth def view_sensitive_data(): return "Top secret information"

2. Caching Decorator

def cache(func): cached_results = {} def wrapper(*args): if args in cached_results: print(f"Cache hit for {args}") return cached_results[args] result = func(*args) cached_results[args] = result print(f"Cache miss for {args}") return result return wrapper @cache def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2)

3. Retry Decorator

def retry(max_attempts=3): def decorator(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 @retry(max_attempts=3) def unreliable_network_call(): import random if random.random() < 0.7: raise ConnectionError("Network error") return "Success!"

Class Decorators

You can also use decorators with classes:

def add_methods(cls): def greet(self): return f"Hello, I'm {self.name}" cls.greet = greet return cls @add_methods class Person: def __init__(self, name): self.name = name person = Person("Alice") print(person.greet()) # Output: Hello, I'm Alice

Built-in Decorators

Python provides several useful built-in decorators:

class MyClass: def __init__(self): self._value = 0 @property def value(self): return self._value @value.setter def value(self, new_value): if new_value < 0: raise ValueError("Value must be positive") self._value = new_value @staticmethod def utility_function(): return "This doesn't need an instance" @classmethod def create_default(cls): return cls()

Best Practices

  1. Preserve function metadata using functools.wraps:
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Decorator logic here return func(*args, **kwargs) return wrapper
  1. Keep decorators simple and focused on a single responsibility
  2. Use decorators sparingly - too many can make code hard to understand
  3. Document your decorators clearly

Conclusion

Decorators are a powerful tool for writing clean, reusable code. They allow you to separate concerns and add functionality without modifying existing code. Master them, and you'll write more elegant Python programs!

Ready to Start Learning Python?

Join thousands of students mastering Python with our structured courses

Start Your Journey