Python的修饰器(decorator)是一个非常强大的功能,一种优秀的设计模式,将重复的内容抽象出来,赋予一个函数其他的功能,但又不去改变函数自身。使得代码极其简洁,易于维护。但其本质上还是一个语法糖

现实问题

在实际的项目中,经常会遇到需要在原有项目上加一些功能,但是这些功能可能很快被删除掉,如何实现这种要求呢?下面举例一个简单的函数

def foo():
    print('hello')

如果想打印该函数的执行时间:

from time import time

def foo():
    t1 = time()
    print('hello')
    t2 = time()
    print(t2 - t1)

这种在原有函数里面加代码,也不符合开闭原则,那么我可以新增加一个函数:

from time import time

def run_time(func):
    def wrap():
        t1 = time()
        func()
        t2 = time()
        print(t2 - t1)
    return wrap

这么写的确不用修改原来的代码,但是新增函数还是要被调用的。Python参照Java,提供了注解来优化该场景,上述场景可以简写如下

from time import time


def run_time(func):
    def wrap():
        t1 = time()
        func()
        t2 = time()
        print(t2 - t1)
    return wrap

@run_time
def foo():
    print('hello')

修饰函数

Python注解修饰函数时,函数定义的不同,注解的实现也不同

无参数无返回

上面的例子就是无参数无返回值

无参数有返回

from time import time

def run_time(func):
    def wrap():
        t1 = time()
        r = func()
        t2 = time()
        print(t2-t1)
        return r
    return wrap

@run_time
def foo():
    return 'hello'

print(foo())

有参数无返回

有参数无返回这种情况较少

from time import time

def run_time(func):
    def wrap(a, b):
        t1 = time()
        func(a, b)
        t2 = time()
        print(t2 - t1)

    return wrap

@run_time
def foo(a, b):
    print(a + b)

foo(2, 45)

有参数有返回

from time import time

def run_time(func):
    def wrap(a, b):
        t1 = time()
        r=func(a, b)
        t2 = time()
        print(t2 - t1)
        return r
    return wrap

@run_time
def foo(a, b):
    return a + b

print(foo(2, 45))

装饰器带参数

有时候装饰器可能需要一些参数,这个场景更复杂,具体实现也就是多层套娃来容纳不同的参数

def namedDecorator(name):
    def run_time(func):
        def wrap(a, b):
            print('this is:{}'.format(name))
            r = func(a, b)
            return r
        return wrap
    return run_time

@namedDecorator("装饰器带参数")
def foo(a, b):
    return a + b

print(foo(2, 45))

消除副作用

上述的装饰后,foo函数已经不是原来的foo了,已经是被装饰后的函数了

def namedDecorator(name):
    def run_time(func):
        def wrap(a, b):
            '''my decorator'''
            print('this is:{}'.format(name))
            r = func(a, b)
            return r
        return wrap
    return run_time

@namedDecorator("装饰器带参数")
def foo(a, b):
    """example docstring""" 
    return a + b

print(foo(2, 45))
print(foo.__name__, foo.__doc__)

返回

this is:装饰器带参数
47
wrap my decorator

使用@wraps装饰器可以消除这种影响

from functools import wraps


def namedDecorator(name):
    def run_time(func):
        @wraps(func)
        def wraper(a, b):
            '''my decorator'''
            print('this is:{}'.format(name))
            r = func(a, b)
            return r

        return wraper

    return run_time

@namedDecorator("装饰器带参数")
def foo(a, b):
    """example docstring"""
    return a + b


print(foo(2, 45))
print(foo.__name__, foo.__doc__)

执行结果

this is:装饰器带参数
47
foo example docstring

多个装饰器

如果想要多个装饰器装饰同一个函数会怎样呢?

执行顺序

from time import time


def run_time_1(func):
    def wrap(a, b):
        r = func(a, b)
        print('run_time_1')
        return r
    return wrap

def run_time_2(func):
    def wrap(a, b):
        r = func(a, b)
        print('run_time_2')
        return r
    return wrap

@run_time_1
@run_time_2
def foo(a, b):
    return a + b

print(foo(2, 45))

返回结果:

run_time_2
run_time_1
47

结论:离被修饰函数最近的一侧先执行

包裹形式

我们在上面的代码上加几行代码

from time import time


def run_time_1(func):
    def wrap(a, b):
        """run_time_1 docstring"""
        print(func.__name__, func.__doc__)
        r = func(a, b)
        print('run_time_1')
        return r
    return wrap

def run_time_2(func):
    def wrap(a, b):
        """run_time_2 docstring"""
        r = func(a, b)
        print('run_time_2')
        return r
    return wrap

@run_time_1
@run_time_2
def foo(a, b):
    """foo docstring"""
    return a + b

print(foo(2, 45))
print(foo.__name__, foo.__doc__)

输出结果:

wrap run_time_2 docstring
run_time_2
run_time_1
47
wrap run_time_1 docstring

结论:这是洋葱一样,一层包一层的结构
当然这可以使用@wrap消除影响,就不加代码了

修饰类

装饰器还可以装饰类,本质不变,都是执行一段自定义代码后再返回,可以用套娃,洋葱打比方

def decorator(cls):
    print("这里可以写被装饰类新增的功能")
    return cls

@decorator
class A(object):
    def __init__(self):
        pass

    def test(self):
        print("test")

执行结果

这里可以写被装饰类新增的功能

与装饰函数不同的是,被装饰的函数需要调用下,被装饰的类,不需要调用,申明阶段即可发挥作用

参考

版权声明:本文为儿立之年原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/willsdu/p/16422647.html