tengwang0318 / article

write some articles to share my experience

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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等,就不一一叙述,可以自行去看文档即可