🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
REFERENCEpython

python Documentation

LOADING ENGINE...

Decorators

AI & DATA SCIENCE // decorators

A decorator is a function that wraps another function to add behavior before, after, or around it, without changing the original function's source code.

Syntax

@my_decorator
def func():
    ...

# equivalent to:
func = my_decorator(func)

Deep Dive Course

The @decorator syntax above a function definition is just syntactic sugar: it passes the function being defined into the decorator function, and rebinds the original name to whatever the decorator returns — usually a wrapper function that calls the original plus some extra behavior. This is how libraries add logging, timing, caching, authentication checks, or route registration to a function without touching its internal logic, and it's the same mechanism behind built-in decorators like staticmethod and property.

1Understanding Decorators

The @decorator syntax above a function definition is just syntactic sugar: it passes the function being defined into the decorator function, and rebinds the original name to whatever the decorator returns — usually a wrapper function that calls the original plus some extra behavior. This is how libraries add logging, timing, caching, authentication checks, or route registration to a function without touching its internal logic, and it's the same mechanism behind built-in decorators like staticmethod and property.

💡

Always apply functools.wraps to your wrapper function inside a decorator — without it, the decorated function loses its original name, docstring, and metadata, which breaks introspection tools, help(), and debugging.

editor.html
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

@timer
def slow_add(a, b):
    time.sleep(0.1)
    return a + b

print(slow_add(2, 3))
localhost:3000

2Practical Example

Here is a real-world application of Decorators showing how it is used in production Python code.

editor.html
def require_positive(func):
    def wrapper(n):
        if n < 0:
            raise ValueError("n must be positive")
        return func(n)
    return wrapper

@require_positive
def square_root(n):
    return n ** 0.5

print(square_root(9))
localhost:3000

3Best Practices

Follow these guidelines when working with Decorators:

1. Use functools.wraps(func) on the inner wrapper function so decorated functions keep their original name and docstring

2. Have the wrapper accept and forward *args and **kwargs so the decorator works on functions with any signature

3. Keep each decorator focused on one cross-cutting concern (timing, logging, caching) so they can be combined and reused independently

⚠️

Tip: Always apply functools.wraps to your wrapper function inside a decorator — without it, the decorated function loses its original name, docstring, and metadata, which breaks introspection tools, help(), and debugging.

editor.html
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

@timer
def slow_add(a, b):
    time.sleep(0.1)
    return a + b

print(slow_add(2, 3))
localhost:3000

Examples

Example 01Basic Usage
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

@timer
def slow_add(a, b):
    time.sleep(0.1)
    return a + b

print(slow_add(2, 3))
Example 02Advanced Example
def require_positive(func):
    def wrapper(n):
        if n < 0:
            raise ValueError("n must be positive")
        return func(n)
    return wrapper

@require_positive
def square_root(n):
    return n ** 0.5

print(square_root(9))

Best Practices

  • Use functools.wraps(func) on the inner wrapper function so decorated functions keep their original name and docstring
  • Have the wrapper accept and forward *args and **kwargs so the decorator works on functions with any signature
  • Keep each decorator focused on one cross-cutting concern (timing, logging, caching) so they can be combined and reused independently

Interview Question

Why does a decorated function often lose its original name and docstring, and how do you fix that?

Hint: Think about what the @decorator syntax actually replaces.

Applying a decorator rebinds the original function's name to the wrapper function the decorator returns, so introspecting it afterward, like checking its name or docstring attribute, shows the wrapper's metadata instead of the original function's. Decorating the inner wrapper function with functools.wraps(func) copies the original function's name, docstring, and other metadata onto the wrapper, so the decorated function still looks like itself to tools that inspect it.

Exercises

MediumPractice using Decorators in a real scenario.
View Solution
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

@timer
def slow_add(a, b):
    time.sleep(0.1)
    return a + b

print(slow_add(2, 3))

Frequently Asked Questions

Why does a decorated function often lose its original name and docstring, and how do you fix that?

Applying a decorator rebinds the original function's name to the wrapper function the decorator returns, so introspecting it afterward, like checking its name or docstring attribute, shows the wrapper's metadata instead of the original function's. Decorating the inner wrapper function with functools.wraps(func) copies the original function's name, docstring, and other metadata onto the wrapper, so the decorated function still looks like itself to tools that inspect it.

Related Functions

def-keywordarguments-argskeyword-arguments-kwargs