Python Decorator for Python Beginners may be a more abstract concept , Before you know about decorators , We must be familiar with Pythond A feature of the method ：Python The method in can be passed as a parameter to another method like an ordinary variable , Let's take an example ：
def add(x,y): return x+y def sub(x,y): return x-y def apply(func,x,y): return func(x,y)
sub()Used for addition and subtraction . In addition, a method is defined
apply(), This method is special , Its parameters can receive methods as parameters , The first parameter
funcParameters are parameters of the method type ,
x,yThe two parameters are ordinary parameters , These two parameters will be passed to
func()In the method , Final execution
func(x,y)Return results . Let's look at this
applyUse of methods .
print(apply(add,3,4)) # Execution results : 7 print(apply(sub,3,4)) # Execution results ：-1
As can be seen from the above execution results , By calling
apply() Method , We can add and subtract . In terms of execution results , And call this... Alone 2 The execution of two methods has the same effect , But in terms of execution mode, we pass these two methods as parameters to another method for execution .
Based on the above example , If we add a requirement now : requirement
sub Print log when method executes , So how do we deal with , Conventional thinking, we may directly add log printing statements to the two methods, as shown below ：
def add(x,y): logging.info('add logging is run' ) return x+y def sub(x,y): logging.info('sub logging is run' ) return x-y
apply()Method we used it to perform addition and subtraction operations , It is like a common tool to carry the execution of two methods , So now we need to add public content to the method , In fact, it can be transformed directly in this method .
def apply(func,x,y): logging.info('%s logging is run' %func.__name__) return func(x,y) print(apply(add,3,4)) print(apply(sub,3,4))
applyMethod, we added a log statement , In this way, every time you execute
subMethod will execute the log print statement , So as to reduce in
add,subMethod to write log print statements directly , This can streamline the code , Increase of efficiency .
applyThis method performs , It may violate the business logic , For example, I encapsulate a method to call addition , But I have to pass every time
applyThis transit method is used to execute , It will affect the readability of the code .
subMethods have log printing function , And the best of both worlds method that can be realized by directly calling the original method ？ Yes ！ At this time, the decorator began to shine ！
First let's look at an example , Then explain what a decorator is .
import logging logging.basicConfig(level=logging.INFO, format='%(asctime)s %(filename)s[line:%(lineno)d]%(levelname)s%(message)s') def add(x,y): return x+y def logging_tool(func): # Log decorator def wrap(x,y): logging.info(' %s logging is run' % func.__name__) return func(x,y) return wrap add=logging_tool(add) print(add(3,4))
logging_toolParameters can receive methods An additional method is defined in this method
wrapParameter is 2 A common parameter
wrapParameter defines the log print statement , Then return the passed in methods and parameters , Execute the operation result of the original method .
logging_toolThis method returns
wrapMethod , This method supports the log printing function and the original method
addThe summation function of .
add=logging_tool(add)In fact, it is a process of decoration assignment ,
logging_toolIt's a decorator , after
logging_toolMethod after decoration , new
addThe method has the function of log printing and summation , We call the decorated... Again
add(3,4)The following results can be obtained ：
7 2019-07-06 09:08:56,325 decorator_blog.py[line:10]INFO add logging is run
stay python I believe that
pytest Students of unit test framework are interested in
@ Symbols must not be strange , For example, we often unittest You will see the following similar usage ：
@unittest.skip("skip Test2") class Test2(unittest.TestCase): def setUp(self): print("Test2 start") @classmethod def setUpClass(cls): print("Class module start test>>>>>>>>")
@ The symbol is actually a decorator symbol , It can replace the above
add=logging_tool(add) Assignment process . So let's use grammar sugar
@ Symbols to transform the code as follows ：
import logging logging.basicConfig(level=logging.INFO, format='%(asctime)s %(filename)s[line:%(lineno)d]%(levelname)s%(message)s') def logging_tool(func): # Log decorator def wrap(x,y): logging.info(' %s logging is run' % func.__name__) return func(x,y) return wrap @logging_tool def add(x,y): return x+y print(add(3,4))
@Decorative symbols are more concise and clear , It doesn't affect
addMethod definition structure , It also supports log printing , Improve code reuse efficiency , When a new method needs log printing function , You can also continue to add... At the top of the method
@logging_toolthat will do .
addThe parameters are also in the decorator
wrapMethod defines two parameters , But if the new business method has three or more parameters, what should be done , Can you define multiple different decorators ？ Obviously, this is unreasonable , Here we can use the treatment of indefinite parameters , About
loging_toolThe code for parameter modification is as follows ：
def logging_tool(func): # Log decorator def wrap(*args,**kwargs): logging.info(' %s logging is run' % func.__name__) return func(*args,**kwargs) return wrap @logging_tool def add_multi(x,y,z): return x+y+z print(add_multi(1,2,3))
Through the above transformation , But what are the decorated business parameters , We can all receive and process , This greatly improves the flexibility of the decorator .
add. If now I want to print logs according to different levels of logs , So how to deal with ？ First look at the following code ：
def logging_tool(level): # Log decorator def decorator(func): def wrap(*args,**kwargs): if level=='info': logging.info(' %s info logging is run' % func.__name__) elif level=='warn': logging.info(' %s warn logging is run' % func.__name__) else: logging.debug(' %s debug logging is run' % func.__name__) return func(*args,**kwargs) # Return the result of the original business method return wrap # Return to add the function of decorator return decorator # Return the whole result @logging_tool(level="warn") def add_multi(x,y,z): return x+y+z print(add_multi(1,2,3))
Execution results ：
6 2019-07-06 10:39:25,698 decorator_blog.py[line:22]INFO add_multi warn logging is run
level, Then added a method
decoratorUsed to receive business methods , stay
wrapAccording to the internal decorator parameters
levelTo judge, enter different levels of logs .
Using decorators greatly reuses code , But he has a disadvantage that the meta information of the original function is missing , For example, functions
__name__、 parameter list , Look at the example ：
def logging_tool(func): # Log decorator def wrap(x,y): '''logging tool wrap''' logging.info(' %s logging is run' % func.__name__) return func(x,y) return wrap @logging_tool def add(x,y): ''' add function :return: ''' return x+y print(add.__name__) print(add.__doc__)
Execution results ：
wrap logging tool wrap
Through the above example, it is not difficult to find , Original
__name__、 Properties are all displayed in the decorator
wrap The properties of the method replace , Obviously, this is not what we want to see . however , We can go through
@functools.wraps Copy the meta information of the original function to the... In the decorator
func Function , Make the inside of the decorator
func The same meta information as the original function .
import functools def logging_tool(func): # Log decorator @functools.wraps(func) # Keep the meta information of the method def wrap(x,y): '''logging tool wrap''' logging.info(' %s logging is run' % func.__name__) return func(x,y) return wrap @logging_tool def add(x,y): '''add function''' return x+y print(add.__name__) print(add.__doc__)
Execution results ：
add add function
We can see from the above example , Use
@functools.wraps The meta information of the method is retained .
Decorators can be more than functions , It can also be a class , Compared to function decorators , Class decorator has great flexibility 、 High cohesion 、 Encapsulation and other advantages . Using class decorators mainly depends on class
call Method , When using
@ Form to attach a decorator to a function , This method will be called .
class Foo(): def __init__(self,func): self._func=func def __call__(self, *args, **kwargs): print(' Class decorator starts executing ') self._func(*args,**kwargs) print(' Class decorator execution ended ') @Foo def bar(): print('hello world') bar()
Class decorator starts executing hello world Class decorator execution ended
Python Built in some decorators , Such as ：
@staticmethod Next, let's explain the usage of these built-in decorators one by one .
property Decorators are responsible for turning a method into an attribute ,
class Student(object): @property def score(self): return self._score @score.setter def score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value s=Student() s.score=90 print(s.score) # Execution results 90 s.score=101 print(s.score) # Execution results ：score must between 0 ~ 100! s.score='hello' print(s.score) # Execution results ：score must be an integer!
As can be seen from the above example , Use decorators
@property We will
score Method becomes an attribute of a class , Another pass
@score.setter Become an assignable property , If not defined
score It's just a read-only property .
@classmethod Class method ： Define alternative constructors , The first parameter is the class itself （ Parameter names are not limited , It's usually used cls） We combine the familiar
unittest The test framework looks at the following example
import unittest class Test(unittest.TestCase): @classmethod # Class method decorator def setUpClass(cls): print("test class start .....") @classmethod def tearDownClass(cls): print("test class end ....") def setUp(self): print("Test case is testing") def tearDown(self): print("test case is end!") def test_case(self): print("test_case is runing!") if __name__=='__main__': unittest.main()
test class start ..... Test case is testing ---------------------------------------------------------------------- test_case is runing! Ran 1 test in 0.000s test case is end! test class end .... OK
tearDownClassTwo methods , These two conveniences become class methods , That is to say
testWhen a type of use case is executed, it will execute
tearDownClassThe contents of the two methods .
@staticmethod Represents a static method , Static methods differ from ordinary methods in that , Static methods belong to the class , Can be called without instantiation .
class C(object): @staticmethod def f(): print('hello world') C.f() # Running results hello world
Up there we pass
@staticmehod Defines a static method
f() When calling, you can directly call , There is no need to instantiate .
This article is from WeChat official account. - QA A corner （sutune2020）
The source and reprint of the original text are detailed in the text , If there is any infringement , Please contact the email@example.com Delete .
Original publication time ： 2020-10-12
Participation of this paper Tencent cloud media sharing plan , You are welcome to join us , share .