Useful Python Decorators

in #python5 years ago

Python decorators are incredible useful for extending functions with other functionality.

import functools is required for each of these decorators.

Write results to file

def write_result_to_file(file, mode='w', encoding='utf-8', encoding_opts=[]):
    def decorator_write_result_to_file(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            with open(file, mode) as f:
                if 'b' in mode and isinstance(result, str):
                    result = result.encode(encoding, *encoding_opts)
                f.write(result)
            return result
        return wrapper
    return decorator_write_result_to_file

Appending to a file the result of a function call

@write_result_to_file('test.txt', mode='a')
def do_something:
    return 42
a = do_something() # test.txt and a contain 42

Printing a functions run time

import time

def timer(func):
    """Print the runtime of the decorated function"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()    # 1
        value = func(*args, **kwargs)
        end_time = time.perf_counter()      # 2
        run_time = end_time - start_time    # 3
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return value
    return wrapper_timer

Capturing some debug info

def debug(func):
    """Print the function signature and return value"""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]                      # 1
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)           # 3
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")           # 4
        return value
    return wrapper_debug

Execute something after sleeping

def slow_down(seconds=1):
    """Sleep n seconds (or 1 second) before calling the function"""
    def decorator_slow_down(func):
        @functools.wraps(func)
        def wrapper_slow_down(*args, **kwargs):
            time.sleep(seconds)
            return func(*args, **kwargs)
        return decorator_slow_down
    return wrapper_slow_down

Execute multiple times

def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat
    return decorator_repeat