Article Directory
Decorator
@ python built-in grammar will make the call in order to simplify decorator
Action decorators:
greatly simplify the code written for each function to avoid repetitive coding
print log: @log
detection performance: @performance
database transaction: @Transaction
the URL Routing: @post ( '/ register')
1, python in writing no parameters decorator
The decorator nature of Python is a higher-order function that accepts a function as a parameter, and then returns a new function.
Use @ decorator syntax provided in Python, to avoid hand written f = decorate (f) such code.
Consider a @log definition:
def log(f):
def fn(x):
print 'call ' + f.__name__ + '()...'
return f(x)
return fn
For the factorial function, @ log work well:
@log
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)
result:
call factorial()...
3628800
However, the argument is not a function of the call will be given:
@log
def add(x, y):
return x + y
print add(1, 2)
result:
Traceback (most recent call last):
File "test.py", line 15, in <module>
print add(1,2)
TypeError: fn() takes exactly 1 argument (2 given)
Because the add () function takes two arguments, but @log write a function that returns the dead with only one parameter.
@Log make any adaptive function defined parameters may be utilized in the Python * args and ** kw, arbitrary number of arguments always ensure normal call:
def log(f):
def fn(*args, **kw):
print 'call ' + f.__name__ + '()...'
return f(*args, **kw)
return fn
Now, for any function, @ log can work properly.
task:
Please write a @performance, it can print out the time function calls.
CODE:
Calculating a time function call may be recorded before and after the timestamp of the current call, and then calculates the difference between two timestamps.
import time
def performance(f):
def fn(*args, **kw):
t1 = time.time()
r = f(*args, **kw)
t2 = time.time()
print 'call %s() in %fs' % (f.__name__, (t2 - t1))
return r
return fn
@performance
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)
2, python in written arguments decorator
A study on the @log
Decorator:
def log(f):
def fn(x):
print 'call ' + f.__name__ + '()...'
return f(x)
return fn
For decorative function is found, the statement print log can not change (except the function name).
If some function is very important, I want to print out '[INFO] call xxx()...'
, and some less important function, print out the hope that '[DEBUG] call xxx()...'
, this time, log function itself will need to pass 'INFO'
or 'DEBUG'
such a parameter, like this:
@log('DEBUG')
def my_func():
pass
To translate the above definition to invoke higher-order functions, that is:
my_func = log('DEBUG')(my_func)
The above statement seems quite around, then expand it:
log_decorator = log('DEBUG')
my_func = log_decorator(my_func)
The above statement is equivalent to another:
log_decorator = log('DEBUG')
@log_decorator
def my_func():
pass
Therefore, the first log function with parameters a decorator function returns, let the decorator my_func function receives and returns the new function:
def log(prefix):
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s()...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
@log('DEBUG')
def test():
pass
print test()
Results of the:
[DEBUG] test()...
None
For this three-layer nested decorator defined, you can put it apart:
# 标准decorator:
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s()...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
# 返回decorator:
def log(prefix):
return log_decorator(f)
Will open later found, the call will fail, because the definition of the decorator nesting 3, the innermost layer of the outermost wrapper references prefix parameter, so that the closure is split into a regular function call can be difficult . It does not support the closure of the programming language to achieve the same function requires more code.
task:
The previous section @performance
can only print seconds, please @performace
add a parameter to allow incoming 's' or 'ms':
@performance('ms')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
CODE:
import time
def performance(unit):
def perf_decorator(f):
def wrapper(*args, **kw):
t1 = time.time()
r = f(*args, **kw)
t2 = time.time()
t = (t2 - t1) * 1000 if unit=='ms' else (t2 - t1)
print 'call %s() in %f %s' % (f.__name__, t, unit)
return r
return wrapper
return perf_decorator
@performance('ms')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)
3, python decorator perfect in
@decorator can dynamically increase the functional capabilities to achieve, however, through the function, and the original function after @decorator "reform" compared to, in addition to function a little more, but there is no other different places?
In the absence decorator, the printing function name:
def f1(x):
pass
print f1.__name__
Output: f1
There decorator under the circumstances, and then print the function name:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
@log
def f2(x):
pass
print f2.__name__
Output: wrapper
Visible, because the decorator function returns a new function name is not 'f2', but @log internally defined 'wrapper'. This will fail to code-dependent function names. Other properties are also changed decorator __doc__ like function. If you want the caller can not see through the @decorator of "reform" a function, you need to copy some of the attributes of the original function to a new function:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
wrapper.__name__ = f.__name__
wrapper.__doc__ = f.__doc__
return wrapper
Write decorator is very convenient, because we can hardly put all the necessary attributes are a function of the original copied onto a new function, the built-in Python functools can be used to automate the complete "copy" of tasks:
import functools
def log(f):
@functools.wraps(f)
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
Finally it should be noted, because we changed the original function signature (* args, ** kw), therefore, can not get the original parameter information of the original function. Even if we use fixed parameters to decorate the function of only one parameter:
def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper
It may change the original function of the parameter name because the parameter name of the new function is always 'x', the original function defined parameter names are not necessarily called 'x'.
task:
Consider the arguments @ decorator, @ functools.wraps should be placed in which:
def performance(unit):
def perf_decorator(f):
def wrapper(*args, **kw):
???
return wrapper
return perf_decorator
CODE:
import time, functools
def performance(unit):
def perf_decorator(f):
@functools.wraps(f)
def wrapper(*args, **kw):
t1 = time.time()
r = f(*args, **kw)
t2 = time.time()
t = (t2 - t1) * 1000 if unit=='ms' else (t2 - t1)
print 'call %s() in %f %s' % (f.__name__, t, unit)
return r
return wrapper
return perf_decorator
@performance('ms')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial.__name__