Decorator Use In Python
tengwang0318 opened this issue · comments
装饰器详解
装饰器是一个callable的对象,将被装饰的函数当作参数传入装饰器中。装饰器会执行一些操作,返回被装饰函数或者另一个函数或者可调用的对象。
将设我们有一个装饰器,其标识符为decorate
@decorate
def target():
print("running target()")
上述代码相当于
def target():
print("running target()")
target = decorate(target)
在python中函数同样也是一个object,因此可以当作对象进行传入参数。在第二段代码中,target已经不是最开始target对象,而是经过decorate装饰后的对象。
运行下述代码:
def decorate(func):
def inner():
print("running inner")
return inner
@decorate
def target():
print("running target")
target()
其输出结果为
running inner
上述代码可以等价为下述代码,把target这个函数对象传给decorate函数,作为其参数。decorate函数的返回值是其函数里面的一个闭包inner函数对象,此时target函数对象就等价于inner这个闭包对象,运行target(), 就相当于运行inner()。
def decorate(func):
def inner():
print("running inner")
return inner
def target():
print("running target")
target = decorate(target)
target()
因为我们把func当作参数传入decorate函数中,那么我们可以在inner函数中调用func来实现func的操作,那么对代码进行稍微变化
def decorate(func):
def inner():
func()
print("running inner")
return inner
@decorate
def target():
print("running target")
target()
运行结果如下所示:
running target
running inner
看到这些就可以想象出装饰器的功能的强大,我们可以创建日志装饰器等等,从而只需要在运行函数时调用装饰器,就可以自动输出日志,而不是为每个函数都定义输出日志的相关操作。
import time
def clock(func):
def clocked(*args):
start_time = time.time()
result = func(*args)
end_time = time.time()
name = func.__name__
arg_str = ", ".join(repr(arg) for arg in args)
print("use {:.3e} seconds to running {}({})".format(end_time - start_time, name, arg_str))
return result
return clocked
@clock
def factorial(n):
return 1 if n < 2 else n * factorial(n - 1)
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 2) + factorial(n - 1)
factorial(5)
print("-------------------------------")
fibonacci(5)
输出结果如下所示,我们不需要每个函数单独写一个log函数来返回相应的日志,只需要写一个日志的装饰器,用装饰器来装饰相应的函数就可以返回对应的日志。对于在clocked中运行func.__name__
,你可能会觉得func这个对象不是随着clock的生命周期结束而消失吗,为什么可以使用func.__name__
,这是因为func相对于clocked闭包来说是freefom valuable,可以查看闭包和自由变量的文档,即可明白,本文不进行叙述。
use 0.000e+00 seconds to running factorial(1)
use 1.287e-05 seconds to running factorial(2)
use 1.693e-05 seconds to running factorial(3)
use 1.884e-05 seconds to running factorial(4)
use 2.098e-05 seconds to running factorial(5)
-------------------------------
use 0.000e+00 seconds to running fibonacci(1)
use 0.000e+00 seconds to running factorial(1)
use 2.146e-06 seconds to running factorial(2)
use 5.960e-06 seconds to running fibonacci(3)
use 0.000e+00 seconds to running factorial(1)
use 2.146e-06 seconds to running factorial(2)
use 5.007e-06 seconds to running factorial(3)
use 6.914e-06 seconds to running factorial(4)
use 1.597e-05 seconds to running fibonacci(5)
最后一个问题:
平时在看别人代码时,一些装饰器相关代码都会选择加上@functools.wraps作为闭包的装饰器。如果我们在上述代码的基础上运行以下代码:
print(fibonacci.__name__)
你得到的返回结果是clocked,而不是fibonacci。
解决上述问题,可以通过将functools.wraps()作为装饰器传入至闭包中。
def clock(func):
@functools.wraps(func)
def clocked(*args):
start_time = time.time()
result = func(*args)
end_time = time.time()
name = func.__name__
arg_str = ", ".join(repr(arg) for arg in args)
print("use {:.3e} seconds to running {}({})".format(end_time - start_time, name, arg_str))
return result
return clocked
此时再运行
print(fibonacci.__name__)
你得到的返回结果是fibonacci。
当然python还有好多内置的装饰器,如lur_cache等,就不一一叙述,可以自行去看文档即可