Menu
Courses / python advanced / Decorators & Higher-Order Functions

Decorators & Higher-Order Functions

01 / 10 Part of python advanced




 

    🎓 Advanced · Topic 01
 


 


    Decorators & Higher-Order Functions
 


 


    Learn how to wrap, extend, and transform functions without modifying their source code — one of Python's most powerful meta-programming tools.
 


 

    ⏱ ~50 min
    🔴 Advanced
    🧠 Meta-programming
 


 
 

   

     
     

Functions as First-Class Objects


   

   

In Python, functions are objects — they can be assigned to variables, passed as arguments, and returned from other functions.


   

     

        first_class.py
     

     
def shout(text):
    return text.upper()

# Assign function to variable
yell = shout
print(yell("hello"))   # HELLO

# Pass function as argument
def apply(func, value):
    return func(value)

print(apply(shout, "world"))  # WORLD

# Return function from function
def multiplier(n):
    def inner(x):
        return x * n
    return inner

double = multiplier(2)
print(double(5))   # 10

   

 


 
 

   

     
     

Building a Decorator from Scratch


   

   

A decorator is a function that takes another function and returns an enhanced version of it. The @ syntax is just shorthand for reassignment.


   

     

        decorator_basics.py
     

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

# Apply with @ syntax
@timer
def slow_sum(n):
    return sum(range(n))

slow_sum(1_000_000)
# slow_sum took 0.0412s

# Equivalent without @ syntax:
# slow_sum = timer(slow_sum)

   

 


 
 

   

     
     

Preserving Metadata with functools.wraps


   

   

Without @wraps, the wrapped function loses its name and docstring. Always use it in production decorators.


   

     

        wraps.py
     

     
from functools import wraps

def log(func):
    @wraps(func)          # preserves __name__, __doc__
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log
def greet(name):
    """Say hello."""
    return f"Hello, {name}"

print(greet.__name__)  # greet (not 'wrapper')
print(greet.__doc__)   # Say hello.

   

 


 
 

   

     
     

Decorators with Arguments


   

   

To pass arguments to a decorator, add one more layer — a factory function that returns the actual decorator.


   

     

        decorator_args.py
     

     
from functools import wraps

def repeat(times):               # factory
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hi():
    print("Hi!")

say_hi()   # prints "Hi!" three times

   

   

      💡 Real-world uses
     

Decorators power @app.route in Flask, @login_required in Django, @cache in functools, and @pytest.mark in testing.


   

 


 
 

   

     
     

Stacking Multiple Decorators


   

   

Multiple decorators apply bottom-up: the one closest to the function is applied first.


   

     

        stacking.py
     

     
@log        # applied second (outer)
@timer      # applied first (inner)
def process(data):
    return [x * 2 for x in data]

# Equivalent to: process = log(timer(process))