Python Basics: Decorators
A decorator is a callable object whose argument is another function (the decorated function). The decorator may process the decorated function and then return it, or replace it with another function or callable object.
1@test
2def target():
3 print("run target function!")
It works the same as the following code
1def target():
2 print("run target function!")
3target = test(target) # In the test function, the function will be enhanced or replaced
Function decorators run at import time, while the decorated function only runs when it is explicitly called.
Python does not require variable declarations, but it assumes that a variable assigned to inside a function body is a local variable. This is far better than the behavior of javascript; javascript also does not require variable declarations, but if you forget to declare a variable as local (using var), you may end up accessing a global variable without realizing it.
An example of a decorator — logging program runs
Goal: every time the program runs, output log, output the program’s runtime, results, and so on, and write log to terminal.
1import time
2def log(func):
3 def ff(*args, **kwargs):
4 t0 = time.time()
5 run_time = time.asctime(time.localtime(t0))
6 res = func(*args, **kwargs)
7 cost = time.time() - t0
8 name = func.__name__
9 args_str = ', '.join(repr(arg) for arg in args) # Convert repr into a human-readable form
10 print('[{}] {}({})->{} cust_time={} s'.format(run_time, name, args_str, res, cost))
11 return res
12 return f
13
14@log
15def f(n):
16 if n <= 1:
17 return 1
18 return n * f(n-1)
19
20>> f(5)
21[Fri Mar 20 16:04:05 2020] f(1)->1 cust_time=0.0 s
22[Fri Mar 20 16:04:05 2020] f(2)->2 cust_time=0.0 s
23[Fri Mar 20 16:04:05 2020] f(3)->6 cust_time=0.000997304916381836 s
24[Fri Mar 20 16:04:05 2020] f(4)->24 cust_time=0.000997304916381836 s
25[Fri Mar 20 16:04:05 2020] f(5)->120 cust_time=0.000997304916381836 s
26120
The example above implements this functionality, but it isn’t complete yet, because it doesn’t support keyword arguments, and it also masks attributes of the decorated function such as __name__ and __doc__. Bring in functools.wraps to help the decorator.
1def log(func):
2 @functools.wraps(func)
3 def f(*args, **kwargs):
4 t0 = time.time()
5 run_time = time.asctime(time.localtime(t0))
6 res = func(*args, **kwargs)
7 cost = time.time() - t0
8 name = func.__name__
9 args_str = ', '.join(repr(arg) for arg in args) # Convert repr into a human-readable form
10 print('[{}] {}({})->{} cust_time={} s'.format(run_time, name, args_str, res, cost))
11 return res
12 return f
Parameterized decorators
We’ll often see wrap taking parameters to help out, like
1from functools import lru_cache
2@lru_cache(max_size=16)
3def f(n):
4 if n <= 1:
5 return 1
6 return n * f(n-1)
which is equivalent to
1f = lru_cache(max_size=16)(f)
So if you want to add parameters, you have to add another layer to the wraper function. Still using the example above, in this case you can treat fmt as a parameter:
1import functools, time
2fmt='[{}] {}({})->{} cust_time={} s'
3def log(fmt):
4 def log_f(func):
5 @functools.wraps(func)
6 def f(*args, **kwargs):
7 t0 = time.time()
8 run_time = time.asctime(time.localtime(t0))
9 res = func(*args, **kwargs)
10 cost = time.time() - t0
11 name = func.__name__
12 args_str = ', '.join(repr(arg) for arg in args) # Convert repr into a human-readable form
13 print(fmt.format(run_time, name, args_str, res, cost))
14 return res
15 return f
16 return log_f
References
《Fluent Python》