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