Decorators are a well-known design pattern that is often used in scenarios with faceted requirements, such as insert logging, performance testing, and transaction processing. Decorators are a great way to solve these problems. With decorators, we can isolate and reuse the same code in a large number of functions that have nothing to do with the function itself. In a nutshell, the purpose of decorators is to add extra functionality to an existing object.
1. Where does demand come from
The definition of a decorator is quite abstract, so let’s look at a small example.
def foo():
print('in foo()')
foo()
Copy the code
It’s a boring function, right. But suddenly someone even more boring, let’s call him Mr. B, says I want to see how long it takes to execute this function, okay, so we can do this:
import time
def foo():
start = time.time()
print('in foo()')
time.sleep(2)
end = time.time()
print(f'used:{end - start}')
foo()
Copy the code
Good. The functionality looks impeccable. But B suddenly stopped looking at this function and became more interested in another function called foo2.
What to do? If you copy the above code to foo2, this is a big mistake. And what if B goes on to look at other functions?
2. Constant should change, change is also
Remember, functions are first-class citizens in Python, so we could consider redefining a function timeit, passing it a reference to Foo, then calling foo in timeit and timing it, thus achieving our goal of not changing foo’s definition, and no matter how many functions B looks at, We don’t even have to change the function definition!
import time
def foo():
print('in foo()')
def timeit(func):
start = time.time()
func()
time.sleep(2)
end = time.time()
print('used:', end - start)
timeit(foo)
Copy the code
There seems to be no logical problem, everything is fine and working fine! … Wait, it looks like we changed the calling part of the code. Instead of calling foo(), we’ll call timeit(foo). That way, if foo is called at all N points, you have to change the code at all N points. Or, more extreme, consider that the code that is called somewhere in the code can’t change the situation, such as if the function is something you gave someone else to use.
3. Change as little as possible
In this case, let’s figure out a way not to modify the calling code; Without modifying the calling code, this means that calling foo() needs to have the effect of calling timeit(foo). We could think of assigning timeit to foo, but timeit seems to take an argument… Think of ways to unify the parameters! If timeit(foo) does not call directly, but returns a function consistent with foo’s argument list… We assign the return value of timeit(foo) to foo, and the code that calls foo() doesn’t change at all!
# -* -coding: utF-8 -* -import time def foo(): print('in foo()') # def timeit(func): Def wrapper() = def wrapper(); start = time.time() func() time.sleep(2) end = time.time() print('used:', End - start) # return return wrapper foo = timeit(foo) foo()Copy the code
So, a simple timer is ready! We can do this by simply adding foo = timeit(foo) before calling foo after defining foo, which is the concept of a decorator that looks like foo is decorated by timeit. In this example, the timing of function entry and exit is called an Aspect. This approach to Programming is called aspect-oriented Programming. In contrast to the top-down approach of traditional programming, it is like inserting a piece of logic horizontally into the flow of function execution. Reduce the amount of repetitive code in a particular business area. There are quite a few other terms for aspect programming, but I won’t go into them here. If you’re interested, you can look them up.
This example is for demonstration purposes only, and does not consider foo to have parameters and return values, so it’s up to you to make it better 🙂
As if this code could not be more compact, Python provides a syntactic sugar to reduce the number of characters to be entered.
import time
def timeit(func):
def wrapper():
start = time.time()
func()
time.sleep(2)
end = time.time()
print('used:', end - start)
return wrapper
@timeit
def foo():
print('in foo()')
foo()
Copy the code
Focus on @timeit on line 11. Adding that line to the definition is exactly the same as writing foo = timeit(foo), and don’t think there’s any magic in @. As well as having less character input, there is an added benefit: it gives it a more decorator feel.
As you can see, a decorator in Python is essentially a function that takes another function as an argument and replaces it with a new modified function.
4. Use decorators for functions that take arguments
If the function you want to wrap has arguments, you don’t need to bother. As long as the parameters and return values of the wrapped function are the same as the original function, the wrapped function returns the wrapped function object
import datetime,time def out(func): def inner(*args): start = datetime.datetime.now() func(*args) end = datetime.datetime.now() print(end-start) print("out and inner") return inner @out def myfunc(*args): time.sleep(1) print("args is{}".format(args)) myfunc("lalalal")Copy the code
5. Give the decorator parameters
Passing arguments to the decorator is not difficult either, just an extra layer of wrapping compared to the previous example
#coding:utf-8 def outermost(*args): def out(func): print (" {}". Format (args)) def inner(*args): print("innet start") func(*args) print ("inner end") return inner return out @outermost(666) def myfun(*args): Format (args)) myFun (" Zhangkun ")Copy the code
6. Decorators with class parameters
It doesn’t matter what type the argument is, it doesn’t matter if it’s a class, right
class locker:
def __init__(self):
print("locker.__init__() should be not called")
@staticmethod
def acquire():
print("locker.acquire() static method be called")
@staticmethod
def release():
print("locker.release() static method be called")
def outermost(cls):
def out(func):
def inner():
cls.acquire()
func()
cls.release()
return inner
return out
@outermost(locker)
def myfunc():
print("myfunc called")
myfunc()
Copy the code
7. Apply multiple decorators to a function
A function can have more than one decorator, but pay attention to the order
class mylocker: def __init__(self): print("mylocker.__init__() called.") @staticmethod def acquire(): print("mylocker.acquire() called.") @staticmethod def unlock(): print(" mylocker.unlock() called.") class lockerex(mylocker): @staticmethod def acquire(): print("lockerex.acquire() called.") @staticmethod def unlock(): print(" lockerex.unlock() called.") def lockhelper(cls): def _deco(func): def __deco2(*args, **kwargs): print("before %s called." % func.__name__) cls.acquire() try: return func(*args, **kwargs) finally: cls.unlock() return __deco2 return _deco class example: @lockhelper(mylocker) @lockhelper(lockerex) def myfunc2(self, a, b): Print (" myfunc2() called.") print(a+b) a = example() a.myfunc2(1,2)Copy the code
8. As a class
While decorators can almost always be implemented as functions, in some cases it may be better to use user-defined classes
import time class DerocatorAsClass: def __init__(self,funcation): self.funcation = funcation def __call__(self, *args, **kwargs): Result = self.funcation(*args,**kwargs) print('3333333333') # return result @DerocatorAsClass def foo(): print('in foo()') foo()Copy the code
As in the example above, it is also convenient to use classes as decorators