Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money

How to pass parameters to decorators was a problem that had bothered me for a long time, although the Python version is updated, now this problem is finally solved, for the record.

doubt

First I have a decorator file path helper/log_helper.py

import traceback
from functools import wraps

from loguru import logger


def my_logger(count):
    def step1(foo):
        @wraps(foo)
        def step2(*args, **kwargs):
            try:
                result = foo(*args, **kwargs)
                logger.info(f"{result=},{count=}")
            except Exception:
                logger.exception(traceback.format_exc())

        return step2

    return step1
Copy the code

Then I have a file that needs to reference the decorator demo.py

from helper.log_helper import my_logger


class Demo:
    @my_logger(count=2)
    def main(self):
        return "in main function"


if __name__ == '__main__':
    d = Demo()
    d.main()
Copy the code

The output is as follows

The 2020-10-16 11:43:12. 001 | INFO | helper. Log_helper: step2:18 - the result = 'in the main function', count = 2Copy the code

This decorator simply gets the return value of the current function and the count value passed in.

Okay, now the question is, right?

What if I pass a value to the decorator argument, which means I pass count=2. You think it might be

from helper.log_helper import my_logger

COUNT=2
class Demo:
    @my_logger(count=COUNT)
    def main(self):
        return "in main function"


if __name__ == '__main__':
    d = Demo()
    d.main()
Copy the code

Ok, that does work, and we can use one more simplification

from functools import partial
from helper.log_helper import my_logger

COUNT=2
my_logger = partial(my_logger,count=2)


class Demo:
    @my_logger()
    def main(self):
        return "in main function"


if __name__ == '__main__':
    d = Demo()
    d.main()
Copy the code

Now that we’re done passing arguments, we’re wondering what if the outside world calls the main method of the Demo class and specifies the value of count.

We know that the only way to call a Demo class is to pass an argument to __init__, so that’s the only way to do it,

class Demo:
    def __init__(self):
        count =2
    @my_logger(count=self.count)
    def main(self):
        return "in main function"
Copy the code

But that doesn’t work. We get an error message

NameError: name 'self' is not defined
Copy the code

Can’t use arguments of the form self. in decorators?

Problem solving

There really wasn’t a viable solution before Python3.7.

We know that dataclasses were introduced in Python3.7 to simplify __init__.

Let’s change our code

from functools import partial

from helper.log_helper import my_logger
from dataclasses import dataclass

@dataclass()
class Demo:
    count: int = 2
    logger: my_logger = partial(my_logger, count)

    @logger()
    def main(self):
        return "in main function"


if __name__ == '__main__':
    d = Demo()
    d.main()
Copy the code

If Python3.8 is used, you can simply ignore dataclass

class Demo:
    count: int = 2
    logger: my_logger = partial(my_logger, count)

    @logger()
    def main(self):
        return "in main function"
Copy the code

In this way, we successfully solved the problem. Suddenly, I remembered the problem I had met before, and now it is solved. I hope it will help you.

Afterword.

Adding self to the decorator also works, as suggested by @Cheonnan.

import traceback
from functools import wraps

from loguru import logger


def my_logger(foo):
    @wraps(foo)
    def step2(self,*args, **kwargs):
        try:
            result = foo(self,*args, **kwargs)
            logger.info(f"{result=},{self.count=}")
            return result
        except Exception:
            logger.exception(traceback.format_exc())

    return step2
Copy the code

The caller

from helper.log_helper import my_logger

class Demo:
    def __init__(self):
        self.count= 2

    @my_logger
    def main(self):
        return "in main function"
Copy the code