装饰器(decorator)

装饰器(decorator): 装饰器本质上使用的机制是闭包,只是语法上更方便,是python提供的语法糖。

回顾闭包

让我们先回顾一下闭包的简单情况:

import time
def decorator(fun):
    print('装饰器开始执行')
    def wrapper():
        start_time = time.time()
        fun()
        end_time = time.time()
        print('函数运行时长', end_time - start_time)
    print('装饰器执行结束')
    return wrapper

def foo():
    print('foo 函数开始执行')
    time.sleep(2)
    print('foo say: hello world')
    print('foo 函数执行结束')


new_fun = decorator(foo)  # 这一句在执行完时就已经输出了两个print
new_fun() # 执行封装的闭包

输出结果:

装饰器开始执行
装饰器执行结束
foo 函数开始执行
foo say: hello world
foo 函数执行结束
函数运行时长 2.0009841918945312

简单装饰器

换成装饰器的写法:

def decorator(fun):
    print('装饰器开始执行')
    def wrapper():
        start_time = time.time()
        fun()
        end_time = time.time()
        print('函数运行时长', end_time - start_time)
    print('装饰器执行结束')
    return wrapper

@decorator
def foo():
    print('foo 函数开始执行')
    time.sleep(2)
    print('foo say: hello world')
    print('foo 函数执行结束')

# 注意,在没执行到 foo()这行代码之前,就已经打印了两个print,证明 @decorator 装饰符其实内部已经做了一个 foo = decorator(foo) 的操作。
foo()

输出结果:

装饰器开始执行
装饰器执行结束
foo 函数开始执行
foo say: hello world
foo 函数执行结束
函数运行时长 2.001372814178467

通用装饰器

通用装饰器,即无论被修饰的函数有多少个参数,都能将它正确封装并返回一个有相同参数的新函数。

import time
def decorator(fun):
    print('装饰器开始执行')
    def wrapper(*args, **kwargs):
        start_time = time.time()
        fun(*args, **kwargs)
        end_time = time.time()
        print('函数运行时长', end_time - start_time)
    print('装饰器执行结束')
    return wrapper

@decorator
def foo(secs):
    print('foo 函数开始执行')
    time.sleep(secs)
    print('foo say: hello world')
    print('foo 函数执行结束')

foo(3)

输出结果:

装饰器开始执行
装饰器执行结束
foo 函数开始执行
foo say: hello world
foo 函数执行结束
函数运行时长 3.001002311706543

多个装饰器叠加装饰

当有多个装饰器同时对一个函数装饰时,距离函数近的装饰器先被执行,直到所以装饰器完成装饰。

def double(f):
    def wrapper(*args, **kwargs):
        ret = f(*args, **kwargs) * 2
        return ret
    return wrapper

def local_square(f):
    def wrapper(*args, **kwargs):
        ret = f(*args, **kwargs) * f(*args, **kwargs)
        return ret
    return wrapper

@double
def foo(x):
    return x

@local_square
def bar(x):
    return x


@local_square
@double
def test(x):
    return x


print(foo(3))
print(bar(4))
print(test(3)) # 叠加装饰效果。 先double,后local_square,结果是36。如果反过来,结果就应该是18了。

输出结果:

6
16
36

带参数的装饰器

装饰函数默认就需要有一个参数来指向被装饰函数的引用。这时如果想再加一个参数,以对装饰效果做灵活的控制该如何实现呢。
假设我们有一个简单的函数,输入一个值就返回一个值,然后我们希望有一个装饰函数,对这原函数返回的值再乘以一个倍数,并且这个倍数是一个可以传递的参数。

先用闭包的方式实现

def multiple(f, multi):
    def wrapper(*args, **kwargs):
       return f(*args, **kwargs) * multi
    return wrapper

def foo(x):
    return x

new_foo = multiple(foo, 5)
print(new_foo(2))

输出结果:
10

再用带参数的装饰器来实现

def outter(multi):
    def inner(f):
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs) * multi
        return wrapper
    return inner

@outter(5)
def foo(x):
    return x

print(foo(2))

输出结果:
10

可以看出,带参数的装饰器,相当于先执行了外层函数并传递那个参数:outter(5),其返回的就是一个普通的装饰器(即只有一个形参用来接收被装饰的函数引用)。然后再用这个普通的装饰器对函数进行装饰。

类装饰器

把类当做装饰器,相当于类名(函数引用),生成了类实例后再执行 实例名(函数引用)

class Decorator:
    def __init__(self, f):
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs) * 5
        self._f = wrapper

    def __call__(self, *args, **kwargs):
        return self._f(*args, **kwargs)


def bar(x):
    return x

dec = Decorator(bar) # 类名加括号的调用,相当于实例化一个对象
print(dec(2)) # 实例加括号的调用,相当于调用call方法

@Decorator
def foo(x):
    return x

print(foo(2))

输出结果:
10
10

带参数的类装饰器

class Decorator:
    def __init__(self, multi):
        self._multi = multi

    def __call__(self, f):
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs) * self._multi
        return wrapper


def bar(x):
    return x

obj_dec = Decorator(5) # 类名加括号的调用,相当于实例化一个对象
new_bar = obj_dec(bar) # 对象加括号的调用,相当于调用call方法,这里call方法返回一个装饰器函数
print(new_bar(2))

@Decorator(5)
def foo(x):
    return x

print(foo(2))

输出结果:
10
10

posted @ 2024-12-06 07:50  RolandHe  阅读(6)  评论(0编辑  收藏  举报