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)
add()
and 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 func
Parameters are parameters of the method type ,x,y
The 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 apply
Use 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 add
and 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))
apply
Method, we added a log statement , In this way, every time you execute add
and sub
Method will execute the log print statement , So as to reduce in add,sub
Method to write log print statements directly , This can streamline the code , Increase of efficiency .apply
This method performs , It may violate the business logic , For example, I encapsulate a method to call addition , But I have to pass every time apply
This transit method is used to execute , It will affect the readability of the code .add
and sub
Methods 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_tool
Parameters can receive methods An additional method is defined in this method wrap
Parameter is 2 A common parameter x,y
wrap
Parameter defines the log print statement , Then return the passed in methods and parameters , Execute the operation result of the original method .logging_tool
This method returns wrap
Method , This method supports the log printing function and the original method add
The summation function of .add=logging_tool(add)
In fact, it is a process of decoration assignment ,logging_tool
It's a decorator , after logging_tool
Method after decoration , new add
The method has the function of log printing and summation , We call the decorated... Again add
Method 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 unittest
or 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>>>>>>>>")
above @
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 add
Method 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_tool
that will do .add
The parameters are also in the decorator wrap
Method 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 *args
and **kwargs
loging_tool
The 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 decorator
Used to receive business methods , stay wrap
According to the internal decorator parameters level
To judge, enter different levels of logs .level=“warn”
Using decorators greatly reuses code , But he has a disadvantage that the meta information of the original function is missing , For example, functions docstring
、__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 add
Methodical docstring
、__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()
Execution results
Class decorator starts executing hello world Class decorator execution ended
Python Built in some decorators , Such as :@property
, @classmethod
, @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()
Execution results
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
@classmethod
To decorate setUpClass
and tearDownClass
Two methods , These two conveniences become class methods , That is to say Test
Class test
When a type of use case is executed, it will execute setUpClass
and tearDownClass
The 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 protected] Delete .
Original publication time : 2020-10-12
Participation of this paper Tencent cloud media sharing plan , You are welcome to join us , share .