13-Python-装饰器

1、装饰器的定义

装饰器的本质就是函数,用来装饰其它函数,就是为其它函数添加附加功能。

装饰器原则如下:

  • 不能修改被装饰的函数的源代码
  • 不能修改被装饰的函数的调用方式

2、实现装饰器知识储备

  • 函数即变量
 1 def bar():
 2     print("in the bar")
 3 def foo():
 4     print("in the foo")
 5     bar()
 6 
 7 foo()
 8 
 9 print("----------分割线-----------")
10 
11 def foo1():
12     print("in the foo")
13     bar1()
14 def bar1():
15     print("in the bar")
16 
17 foo1()
18 
19 print("----------分割线-----------")
20 # 这样会报错
21 # def foo2():
22 #     print("in the foo")
23 #     bar2()
24 # foo2()
25 # def bar2():
26 #     print("in the bar")

 

  • 高阶函数
  • 把一个函数名当作实参传递给另外一个函数(在不修改被装饰函数源代码的情况下为其添加功能)
 1 import time
 2 
 3 def bar1():
 4     time.sleep(3)
 5     print("in the bar")
 6 
 7 def test2(func):
 8     start_time = time.time()
 9     func()  # 相当于运行bar1()
10     stop_time = time.time()
11     print("total run time %s" %(stop_time - start_time))
12 
13 test2(bar1)  

 

  • 返回值中包含函数名(不能修改函数的调用方式)
 1 def bar2():
 2     time.sleep(3)
 3     print("in the bar2")
 4 
 5 def test3(func):
 6     print(func)
 7     return func
 8 
 9 print(test3(bar2))  # 获取的是内存地址
10 
11 res = test3(bar2)
12 res()

 

  • 嵌套函数
1 def foo():
2     print("in the foo")
3     def bar():
4         print("in the bar")
5     bar()  # 局部变量只能在其作用域内调用
6 
7 foo()
 1 x = 0
 2 def grandpa():
 3     x = 1
 4     def dad():
 5         x = 2
 6         def son():
 7             x = 3
 8             print(x)  # 最终打印结果为3
 9         son()
10     dad()
11 grandpa()

 

  • 高阶函数 + 嵌套函数 --》装饰器
 1 import time
 2 
 3 
 4 def timer(func):
 5     def deco():
 6         start_time = time.time()
 7         func()
 8         stop_time = time.time()
 9         print("total time is %s" % (stop_time - start_time))
10     return deco  # 返回deco()的内存地址
11 
12 
13 def test1():
14     time.sleep(3)
15     print("in the test1")
16 
17 
18 def test2():
19     time.sleep(3)
20     print("in the test2")
21 
22 #以下可直接用装饰器语法代替
23 timer(test1)  # test1的内存地址赋值给func,返回deco()的内存地址
24 print(timer(test1))  # 返回deco()的内存地址
25 test1 = timer(test1)  # 内存地址赋值给test1
26 test1()  # 相当于执行deco()
27 
28 timer(test2)
29 test2 = timer(test2)
30 test2()
31 
32 print("---------我是分隔符---------")
33 
34 
35 # 装饰器语法如下。(和上面引用的效果一样)
36 @timer  # 相当于test1 = timer(test1)
37 def test1():
38     time.sleep(3)
39     print("in the test1")
40 
41 
42 @timer  # 相当于test1 = timer(test1)
43 def test2():
44     time.sleep(3)
45     print("in the test2")
46 
47 
48 test1()
49 test2()

 

3、动态参数装饰器

def timer(bar):
    def inner(*args, **kwargs):
        start_time = time.time()
        foo_ret = bar(*args, **kwargs)
        end_time = time.time()
        used_time = end_time - start_time
        print(used_time)

        return foo_ret

    return inner


@timer
def foo(*args, **kwargs):
    time.sleep(1)
    print("我的参数:", args, kwargs)
    print("我的运行时间:")

    return "我的返回值"


ret = foo("动态参数装饰器", (1, 2), name="Druid", age=18)
print(ret)

  输出结果如下:

  

 

4、装饰器原理图解

 4.1 被装饰函数没有返回值

  

 

 4.2 被装饰函数有返回值

  

   注意:第二步仅为过程分析量,不作为真实的执行顺序。

5、装饰器固定格式

  装饰器的固定格式如下例所示:

def wrapper(func):
    """
    该函数为装饰器函数
    :param func: 这里的func参数实质是指向被装饰函数的内存地址
    :return:
    """
    def inner(*args, **kwargs):
        """
        该函数为装饰器函数内部函数
        :param args: 实质接收的是被装饰函数的位置参数
        :param kwargs: 实质接收的是被装饰函数的关键字参数
        :return: 返回的是被装饰函数的返回值
        """
        print("这里放被装饰函数执行之前要做的事")
        func_ret = func(*args, **kwargs)  # 被装饰的函数
        print("这里放被装饰函数执行之后要做的事")
        
        return func_ret

    return inner


@wrapper  # 等价于my_func = wrapper(my_func)
def my_func(*args, **kwargs):
    """
    该函数为被装饰函数
    :param args: 接收位置参数
    :param kwargs: 接收关键字参数
    :return: 返回值
    """
    print(*args, **kwargs)

    return ret


ret = my_func()  # 执行原函数,实质是执行inner()。函数返回值保存在变量ret中。

 

6、装饰器修复

  当我们使用装饰器去装饰某个函数时,我们想要引用被装饰函数原私有属性,如__name__、__doc__时,就有问题了,因为我们虽然仍然在执行被装饰函数,但其实执行的是闭包,看下例。

def wrapper(func):
    """
    该函数为装饰器函数
    :param func: 这里的func参数实质是指向被装饰函数的内存地址
    :return:
    """
    def inner(*args, **kwargs):
        """
        该函数为闭包(装饰器函数内部函数)
        :param args: 实质接收的是被装饰函数的位置参数
        :param kwargs: 实质接收的是被装饰函数的关键字参数
        :return: 返回的是被装饰函数的返回值
        """

        func_ret = func(*args, **kwargs)  # 被装饰的函数

        return func_ret

    return inner


@wrapper  # 等价于my_func = wrapper(my_func)
def my_func(*args, **kwargs):
    """
    该函数为被装饰函数
    :param args: 接收位置参数
    :param kwargs: 接收关键字参数
    :return: 返回值
    """
    print(*args, **kwargs)

    return "返回值"


my_func("装饰器没被修复前,被装饰函数原函数的私有属性如__name__、__doc__是获取不到的,如下:")  # 执行原函数,实质是执行inner()
print(my_func.__name__)  # 打印函数的名字
print(my_func.__doc__)  # 打印函数的注释文档

  输出结果如下:

  

  如果仍想使用被装饰函数的原私有属性,那么就可以用装饰器修复:

from functools import wraps


def wrapper(func):
    """
    该函数为装饰器函数
    :param func: 这里的func参数实质是指向被装饰函数的内存地址
    :return:
    """

    @wraps(func)
    def inner(*args, **kwargs):
        """
        该函数为闭包(装饰器函数内部函数)
        :param args: 实质接收的是被装饰函数的位置参数
        :param kwargs: 实质接收的是被装饰函数的关键字参数
        :return: 返回的是被装饰函数的返回值
        """

        func_ret = func(*args, **kwargs)  # 被装饰的函数

        print("装饰器修复不会改变原装饰器的作用")

        return func_ret

    return inner


@wrapper  # 等价于my_func = wrapper(my_func)
def my_func(*args, **kwargs):
    """
    该函数为被装饰函数
    :param args: 接收位置参数
    :param kwargs: 接收关键字参数
    :return: 返回值
    """
    print(*args, **kwargs)

    return "返回值"


my_func("装饰器被修复后,被装饰函数原函数的私有属性如__name__、__doc__就可以正常获取了,如下:")  # 执行原函数,实质是执行inner()
print(my_func.__name__)  # 打印函数的名字
print(my_func.__doc__)  # 打印函数的注释文档

  输出结果如下:

  

 

7、带参数的装饰器 

  需求:很多函数共用一个装饰器,要求随时可以关闭装饰器功能,且尽可能的减少代码修改。 该需求可以用标记位来实现,如下:

import time


FLAG = True


def timmer_out(flag):
    def timmer(func):
        def inner(*args, **kwargs):
            if flag:
                start_time = time.time()
                ret_func = func(*args, **kwargs)
                end_time = time.time()
                used_time = end_time - start_time
                print("函数{name}执行时间:{time}".format(name=func.__name__, time=used_time))
                # print("函数{name}执行时间:{time}".format_map({"name": func.__name__, "time": used_time}))
            else:
                ret_func = func(*args, **kwargs)

            return ret_func

        return inner

    return timmer


@timmer_out(FLAG)  # 第一步,先执行timmer_out(FLAG),得到返回值timmer。第二步执行@timmer,即 my_func1 = timmer(my_func1)
def my_func1():
    time.sleep(1)
    print("my_func1")


@timmer_out(FLAG)
def my_func2():
    time.sleep(1)
    print("my_func2")


@timmer_out(FLAG)
def my_func3():
    time.sleep(1)
    print("my_func3")


my_func1()
my_func2()
my_func3()

  当FLAG置为True时,装饰器功能生效,输出结果如下图所示:

  

  当FLAG置为False时,装饰器功能关闭,输出结果如下图所示:

  

 

8、多个装饰器装饰一个函数

 

def wrapper1(func):
    def inner1():
        print("wrapper1, before func")
        func()
        print("wrapper1, after func")

    return inner1


def wrapper2(func):
    def inner2():
        print("wrapper2, before func")
        func()
        print("wrapper2, after func")

    return inner2


@wrapper2
@wrapper1
def my_func():
    print("function is my func")


my_func()

  注意输出结果:

  

  为什么结果是这样?请看如下分析:

  

  为什么是先执行@wrapper1而不是@wrapeer2呢?因为装饰器在找到被装饰函数会优先执行。

 

posted @ 2017-11-27 23:29  Druid_Py  阅读(180)  评论(0编辑  收藏  举报