</> Code Editor { } Code Formatter

Python Decorators Exercises


1/20

Python Decorators Practice Questions

Correct
0%

What is a Python decorator at its most basic level?


The term "Decorator" comes from the Decorator Design Pattern. It allows you to "wrap" an existing piece of code in a new layer of logic.

Think of it like a Gift Wrap:

  • The Function is the gift inside.
  • The Decorator is the wrapping paper and the bow.
  • When you receive the gift, you still get the item inside, but the appearance and presentation have been modified.
Key Point: Decorators allow you to follow the DRY (Don't Repeat Yourself) principle by separating "boilerplate" code (like logging or security checks) from the main logic.

Quick Recap of Python Decorators Concepts

If you are not clear on the concepts of Decorators, you can quickly review them here before practicing the exercises. This recap highlights the essential points and logic to help you solve problems confidently.

Python Decorators — Definition, Mechanics, and Usage

A Decorator is a design pattern that allows you to "wrap" a function or a class to extend its behavior without permanently modifying its source code. In Python, this is made possible because functions are first-class objects, meaning they can be passed as arguments, nested inside other functions, and returned just like variables.

Think of a decorator as an "envelope." The original function is the letter inside. The envelope (decorator) can have its own data and behavior (like postage stamps or security seals) that are processed before the letter itself is ever read.

Why Use Decorators — Key Benefits

Decorators allow developers to write cleaner, more modular code by separating "meta-programming" tasks from the actual logic of the application. This ensures that your core functions remain focused on their primary purpose.

BenefitExplanation
Logic SeparationSeparates "administrative" tasks like logging and security from core business logic.
Code ReusabilityWrite a feature once (like a performance timer) and apply it to hundreds of functions with one line.
DRY Principle"Don't Repeat Yourself"—prevents the need to copy-paste validation logic into every function definition.
Metadata ControlUsing functools.wraps ensures your function maintains its identity (name, docstrings) for debugging.

By using decorators, you can add features like memoization (caching), rate limiting, or authentication to any existing function without needing to rewrite it from scratch.

1. The Foundation: Higher-Order Functions

To build a decorator, you must understand how functions can return other functions. This "closure" property allows the inner function to remember the state of the outer function even after the outer function has finished executing. This is the "engine" that makes a decorator work.

def parent(func):
    def wrapper():
        print("Wrapper: Action before function.")
        func() # Executing the passed-in function
        print("Wrapper: Action after function.")
    return wrapper

@parent
def say_hello():
    print("Core: Hello World!")

# Calling say_hello now actually calls the 'wrapper'
say_hello()

2. Universal Decorators with *args and **kwargs

A professional decorator must be able to handle any function, regardless of its signature. If you do not use *args and **kwargs, your decorator will only work on functions with a specific number of arguments. By using these placeholders, you ensure your decorator is "universal."

import functools

def debug(func):
    # functools.wraps preserves the original function's name and metadata
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Executing {func.__name__} with args:{args} kwargs:{kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} finished. Result: {result}")
        return result
    return wrapper

@debug
def calculate_tax(amount, rate, discount=0):
    return (amount * rate) - discount

# This works for any combination of positional and keyword arguments
calculate_tax(100, 0.15, discount=5)

3. Practical Case: Performance Benchmarking

One of the most common uses for decorators in production is measuring execution time. This allows you to identify "bottlenecks" in your application without adding time.perf_counter() logic inside every single function you want to test.

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        
        # Capture the actual return value of the function
        value = func(*args, **kwargs)
        
        end_time = time.perf_counter()
        run_time = end_time - start_time
        print(f"Performance: {func.__name__!r} took {run_time:.4f} secs")
        
        # Return the value so the rest of the app doesn't break
        return value
    return wrapper

@timer
def heavy_computation():
    return sum([i**2 for i in range(1000000)])

result = heavy_computation()

4. Advanced: Decorators with Arguments (The Factory Pattern)

Sometimes the decorator itself needs configuration—such as a "repeat" decorator that needs to know how many times to execute. This requires a Decorator Factory: a function that accepts arguments and returns the actual decorator.

def repeat(num_times):
    def decorator_repeat(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(num_times):
                # We call the function multiple times
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")

# This will print the greeting 3 times because of the argument passed to @repeat
greet("Alice")

Best Practices With Python Decorators

  • Always use @functools.wraps: Without this, your decorated functions will lose their original identity (like __name__ and __doc__), making it very difficult to debug.
  • Preserve Return Values: Always ensure your wrapper captures result = func(*args, **kwargs) and returns it at the end.
  • Keep Decorators Focused: A decorator should do one thing (e.g., just logging or just timing). If you need both, stack them!
  • Order of Execution: When stacking decorators (e.g., @auth followed by @log), the top-most decorator is the "outermost" layer. It executes first and finishes last.

Summary: Key Points

  • Decorators allow you to modify or enhance the behavior of a function without changing its source code.
  • They leverage Python's First-Class Functions, allowing logic to be passed as arguments and returned as results.
  • The @ symbol is syntactic sugar that makes the wrapping process readable and clean.
  • *args and **kwargs are essential for creating "universal" decorators that work with any function signature.
  • functools.wraps is a professional requirement to ensure function metadata (names and docstrings) is preserved.
  • Chaining allows you to layer multiple functionalities, such as authentication, logging, and performance tracking, on a single target.


About This Exercise: Python – Decorators

At Solviyo, we see Python decorators as one of those features that truly change how you write and structure code. A decorator allows you to enhance or modify the behavior of a function without touching its original implementation. Behind the familiar @ syntax lies a powerful concept built on first-class functions and closures. In this exercise section, we focus on helping you understand that concept clearly, step by step, through practical Python exercises and well-explained MCQs.

These Python decorator exercises are designed not just to show how decorators work, but why they exist and when they should be used. You’ll move from simply applying decorators to confidently building your own. Along the way, we explain how functions are passed around, wrapped, and executed, so the “magic” becomes predictable and easy to reason about.

What You Will Learn

By working through this section, you will develop a solid, practical understanding of decorators in Python. The exercises with answers will help you explore:

  • First-Class Functions: How Python treats functions as objects that can be passed and returned.
  • Closures: Why a decorator can remember the function it wraps and any associated state.
  • The @ Decorator Syntax: What really happens when a decorator is applied to a function.
  • Decorators with Arguments: Using *args and **kwargs to write flexible, reusable decorators.
  • Preserving Metadata: Correctly using functools.wraps to keep function names and docstrings intact.

Why Python Decorators Matter

Decorators are essential for writing clean, maintainable Python code. They help enforce separation of concerns by keeping cross-cutting logic—such as logging, caching, authentication, or timing—out of your core business logic. Instead of repeating the same code across multiple functions, you write it once and reuse it everywhere.

In real-world projects, especially frameworks and large applications, decorators make code easier to maintain, test, and scale. Mastering this topic is a clear step toward writing professional-grade Python software.

Start Practicing

Each Solviyo Python decorator exercise includes clear explanations and answers so you can confidently apply what you learn. If you want a refresher on functions and scope, review the quick recap before starting. Then dive in and strengthen your understanding of Python decorators through hands-on practice.