Decorators

A decorator is a function that takes an other function as an argument and adds some kinda functonallity and then returns another function.

def decorator_function(original_function):
    def wrapper_function():
        return original_function()
    return wrapper_function

def display():
    print("Display Function Executed")
decorated_display = decorator_function(display)
decorated_display()
Display Function Executed
def decorator_function(original_function):
    def wrapper_function():
        print("Wrapper Executed this before {}".format(original_function.__name__))
        return original_function()
    return wrapper_function

def display():
    print("Display Function Executed")
decorated_display = decorator_function(display)
decorated_display()
Wrapper Executed this before display
Display Function Executed
def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        print("Wrapper Executed this before {}".format(original_function.__name__))
        return original_function(*args, **kwargs)
    return wrapper_function
@decorator_function
def display():
    print("Display Function Executed")
display()
Wrapper Executed this before display
Display Function Executed
@decorator_function
def display_info(name, age):
    print("Display_info executed with arguments: ({}, {})".format(name, age))
display_info("John", 25)
Wrapper Executed this before display_info
Display_info executed with arguments: (John, 25)
decorated_display = decorator_function(display)
decorated_display()

is similar to

@decorator_function
def display():
    print("Display Function Executed")
# the above representation exactly says:
display = decorator_functon(display)

Decorator with Class

class decorator_class(object):
    
    def __init__(self, original_function):
        self.original_function = original_function
    def __call__(self, *args, **kwargs):
        print("Call method executed before {}".format(self.original_function.__name__))
        return self.original_function(*args, **kwargs)
@decorator_class
def display():
    print("Display Function Executed")

display()
Call method executed before display
Display Function Executed
@decorator_class
def display_info(name, age):
    print("Display_info executed with arguments: ({}, {})".format(name, age))
    
display_info("John", 25)
Call method executed before display_info
Display_info executed with arguments: (John, 25)

Function for logging the arguments passed to a particulat func

def my_logger(orig_func):
    import logging
    logging.basicConfig(filename='{}.log'.format(orig_func.__name__,
                                                level= logging.INFO))
    
    def wrapper(*args, **kwargs):
        logging.info(
        'Ran with Args: {}, and Kwargs {}'.format(args, kwargs))
        
    return wrapper
@my_logger
def display_info(name, age):
    print("Display_info executed with arguments: ({}, {})".format(name, age))
display_info("Oill", 45)

Function for printing execution time of a function

def my_timer(orig_func):
    import time

    def wrapper(*args, **kwargs):
        t1 = time.time()
        result = orig_func(*args, **kwargs)
        t2 = time.time() - t1
        print('{} ran in: {} sec'.format(orig_func.__name__, t2))
        return result

    return wrapper
@my_timer
def display_info(name, age):
    print("Display_info executed with arguments: ({}, {})".format(name, age))
display_info("Kill", 15)
Display_info executed with arguments: (Kill, 15)
display_info ran in: 5.0067901611328125e-05 sec

Applying Multiple decorators for a function

@my_logger
@my_timer
def display_info(name, age):
    print("Display_info executed with arguments: ({}, {})".format(name, age))

is completely similar to

display_info = my_logger(my_timer(display_info))
@my_logger
@my_timer
def display_info(name, age):
    print("Display_info executed with arguments: ({}, {})".format(name, age))
# adds the result in the log file
display_info(2, 4)
@my_timer
@my_logger
def display_info(name, age):
    print("Display_info executed with arguments: ({}, {})".format(name, age))
display_info()
wrapper ran in: 0.0003368854522705078 sec

The is an issue running up with the sequence or order of placing decorators.

# Decorators
from functools import wraps


def my_logger(orig_func):
    import logging
    logging.basicConfig(filename='{}.log'.format(orig_func.__name__), level=logging.INFO)

    @wraps(orig_func)
    def wrapper(*args, **kwargs):
        logging.info(
            'Ran with args: {}, and kwargs: {}'.format(args, kwargs))
        return orig_func(*args, **kwargs)

    return wrapper


def my_timer(orig_func):
    import time

    @wraps(orig_func)
    def wrapper(*args, **kwargs):
        t1 = time.time()
        result = orig_func(*args, **kwargs)
        t2 = time.time() - t1
        print('{} ran in: {} sec'.format(orig_func.__name__, t2))
        return result

    return wrapper

import time


@my_logger
@my_timer
def display_info(name, age):
    time.sleep(1)
    print('display_info ran with arguments ({}, {})'.format(name, age))

display_info('Tom', 22)
display_info ran with arguments (Tom, 22)
display_info ran in: 1.0008461475372314 sec

Reference

Corey Schafer