Wieder was gelernt

Function Decorators in Python

Tags: Python
2024-12-15

The description of function decorators in the language reference is quite dense, so I’m teasing it apart here with two examples from 5 Custom Python Decorators For Your Projects by NeuralNine.

Decorator without parameters

def time_logger(func):
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print("Time taken:", end - start)
        return result
    return wrapper


@time_logger
def process_input(n):
    # whatever

Decorator expressions are evaluated when the function is defined, in the scope that contains the function definition. The result must be a callable,

The result of the expression here is time_logger, i.e. a reference to the function time_logger, not the result of that function.

which is invoked with the function object as the only argument.

Then time_logger(process_input) is called, which returns an instance of wrapper closed over process_input.

The returned value is bound to the function name instead of the function object.

Finally process_input is assigned a reference to that instance of wrapper, so any call to process_input will invoke the wrapper instance instead.

Decorator with parameters

def retry(retries=3, exception=Exception):
    def decorator(func):
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < retries:
                try:
                    return func(*args, **kwargs)
                except exception as e:
                    attempts += 1
                    print(f"Failed ({attempts}/{retries})")
            raise exception
        return wrapper
    return decorator

@retry(retries=5, exception=ValueError)
def error_prone_function():
    # whatever

Decorator expressions are evaluated when the function is defined, in the scope that contains the function definition. The result must be a callable,

Here the decorator expression is a function call, not just the name of the function, so the result of the expression is the return value of retries(...), i.e. decorator closed over retries=5 and exception=ValueError.

which is invoked with the function object as the only argument.

This will then be called with error_prone_function as an argument, which will return wrapper and the rest is as before.

Decorators which can be used with or without parameters

Some decorators (e.g. functools.lru_cache) can be used with or without parameters.

In this case the function must check its parameters (if there is only one and its a callable then it was used without parameters), which is a bit of a hack.