Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to pass arguments to a decorator while calling a function in Python?

Does anyone know if it's possible to pass arguments to a decorator while calling a function in Python ?

Until now, I only saw this kind of thing on a function definition :

@decorator("This is a decorator", 66)
def func(a: int, b: int) -> None:
    pass

But I was wondering if it was possible to do it while calling a function.

something like this :

func(a, b)(66)  # 66 is passed by argument to @decorator

For those who are wondering why I want to do this, it's because I'm proceeding someone else's work who used decorators a lot in his code. But until now he only had to pass arguments at function definition because the function was only called once. The decorators are used to print information logs about the context the function is used but in my case, since the function can be called at different places, the context can be different in function of where the function is called.

like image 707
Autechre Avatar asked Mar 03 '26 18:03

Autechre


1 Answers

As others wrote here, a decorator is a syntactic sugar (i.e., makes programs easier to read, write, or understand) of a function that receives another function as a parameter and activates it from inside.

So, calling this “Add()” function with the decorator, like this:

@wrapper()
def Add(x: int, y: int):
    return x + y

It is just like calling the “wrapper” function with the “Add” function as a variable. Like this:

 wrapper(Add)(x,y) # pass x,y to wrapper that pass it to Add function.
|wrapper|
|----Add----|()

The best way (I think) to add parameters to a decorator, is by nesting it all under another function that holds a child decorator. For example:

@deco_maker(msg: str)
def Add(x: int, y: int):
    return x + y

will be this:

 deco_maker(msg)(Add)(x,y)
|--wrapper-|
|-wrapper_func-|
|---------Add-------|

Here is a simple wrapper decorator that log's function calls, without parameters, which can look like this:

def wrapper(func: Callable):
    def wrapper_func(*args, **kwargs):
        logging.DEBUG f"Function '{func.__name__}' called with args: {[str(arg) for arg in args]}."
        value = func(*args, **kwargs)
        return value
    return wrapper_func

and here is the extended decorator with relevant logging parameters (log name and level for more flexibility):

def log_func_calls(logger_name: str, log_level: int):
    def wrapper(func: Callable):
        def wrapper_func(*args, **kwargs):
            logger = logging.getLogger(logger_name)
            logger.log(
                    level=log_level,
                    msg=f"Function '{func.__name__}' called with args: {[str(arg) for arg in args]}."
                    )
            value = func(*args, **kwargs)
            return value
        return wrapper_func
    return wrapper

Here is a full code example for a parameterized decorator for logging function calls, and the log file output print after it.

Example:

import logging
from typing import Callable

# define app logger with file and console handlers
def setup_logging():
    logger = logging.getLogger('test_app')
    logger.setLevel(logging.DEBUG)
    # create file handler which logs even debug messages
    fh = logging.FileHandler('test.log')
    fh.setLevel(logging.DEBUG)
    # create formatter and add it to the file handler
    formatter = logging.Formatter('{asctime} | {name} | {levelname:^8s} | {message}', style='{')
    fh.setFormatter(formatter)
    # add the handler to the logger
    logger.addHandler(fh)
    return logger

# define a log decorator to trace function calls
def log_func_calls(logger_name: str, log_level: int):
    def wrapper(func: Callable):
        def wrapper_func(*args, **kwargs):
            logger = logging.getLogger(logger_name)
            logger.log(
                    level=log_level,
                    msg=f"Function '{func.__name__}' called with args: {[str(arg) for arg in args]}."
                    )
            value = func(*args, **kwargs)
            return value
        return wrapper_func
    return wrapper

# sample usage 1
@log_func_calls(logger_name='test_app', log_level=logging.DEBUG)
def Add(x: int, y: int):
    return x + y

# sample usage 2
@log_func_calls(logger_name='test_app', log_level=logging.DEBUG)
def Sub(x: int, y: int):
    return x - y

# a test run
def main():
    logger = setup_logging()
    logger.info("<<< App started ! >>>")
    print(Add(50,7))
    print(Sub(10,7))
    print(Add(50,70))
    logger.info("<<< App Ended ! >>>")

if __name__ == "__main__":
    main()

And the log output:

...
2022-06-19 23:34:52,656 | test_app |  DEBUG   | Function 'Add' called with args: ['50', '7'].
2022-06-19 23:34:52,656 | test_app |  DEBUG   | Function 'Sub' called with args: ['10', '7'].
2022-06-19 23:34:52,657 | test_app |  DEBUG   | Function 'Add' called with args: ['50', '70'].
...
like image 68
Ofer Calvo Avatar answered Mar 05 '26 06:03

Ofer Calvo