Python装饰器

学习视频:

https://www.bilibili.com/video/BV1SZ4y1s7cv?p=1

学习笔记:

装饰器:

 

装饰器(decorator)的本质:函数闭包(function closure)语法糖(Syntactic sugar)
 

代码段1:

求100以内的奇数并计算此过程耗费的时间
import time
"""
缺点: 
1、函数逻辑(查找奇数)和辅助功能(记录时间)耦合在一起
2、不方便修改,容易引起bug
>>>>>>>>能不能将辅助功能从主要功能函数中抽离出来?
"""

def print_odds():
    """
    输出0~100之间所有奇数,并统计函数执行时间
    """
    start_time = time.perf_counter()   # 起始时间
    # 查找并输出所有奇数
    for i in range(100):
        if i % 2 == 1:
            print(i)
    end_time = time.perf_counter()     # 结束时间
    print("it takes {} s to find all the olds".format(end_time - start_time))

if __name__ == '__main__':
    print_odds()

 代码段2:

该段代码将代码段1进行了优化,将代码解耦为一个辅助函数和主要函数

import time

"""
将辅助功能(记录时间)抽离成一个辅助函数count_time,在辅助函数count_time中调用主要功能函数print_odds.
优点: 解耦,函数职责分离.
缺点: 要通过辅助函数来调用主要功能函数,不方便.
>>>>>>>>>我们的目标: 能不能在调用主要功能函数时自动完成对时间的统计?
"""


def count_time(func):
    """
    统计某个函数的运行时间
    """
    start_time = time.perf_counter()  # 起始时间
    func()  # 执行函数
    end_time = time.perf_counter()  # 结束时间
    print("it takes {} s to find all the olds".format(end_time - start_time))


def print_odds():
    """
    输出0~100之间所有奇数,并统计函数执行时间
    """
    for i in range(100):
        if i % 2 == 1:
            print(i)


if __name__ == '__main__':
    # print_odds() # 只运行主要函数只会打印出100以内的奇数,无时间统计
    count_time(print_odds)

代码段3:

引入函数闭包

 

什么是函数闭包?

函数式语言(函数是一等公民,可作为变量使用)中的术语
函数闭包:一个函数,其参数和返回值都是函数.
• 用于增强函数功能
• 面向切面编程(AOP)

 

 

import time
"""
通过闭包增强主要功能函数print_odds,给它增加一个统计时间功能
缺点: 需要显式进行闭包增强
"""


def print_odds():
    """
    输出0~100之间所有奇数,并统计函数执行时间
    """
    for i in range(100):
        if i % 2 == 1:
            print(i)


# 闭包函数本质上是一个函数
# 闭包函数的传入参数和返回值也都是函数
# 闭包函数的返回值函数是对传入函数进行增强后的结果
def count_time_wrapper(func):
    """
    闭包,用于增强函数func: 给函数func增加统计时间的功能
    """

    def improved_func():
        start_time = time.perf_counter()   # 起始时间
        func()                      # 执行函数
        end_time = time.perf_counter()     # 结束时间
        print("it takes {} s to find all the olds".format(end_time - start_time))

    return improved_func


if __name__ == '__main__':
    # 调用count_time_wrapper增强函数
    print_odds = count_time_wrapper(print_odds)
    print_odds()# improved

代码段2和代码段3的区别:

代码段2是通过调用辅助函数来实现主功能

代码段3是通过调用主要函数来实现辅助功能

 

 代码段4:

引入装饰器
什么是语法糖?
  指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。
  语法糖没有增加新功能,只是一种更方便的写法
  语法糖可以完全等价地转换为原本非语法糖的代码
  装饰器在第一次调用被装饰函数时进行增强

解释:
装饰器在第一次调用被装饰函数时进行增强
• 增强时机?在第一次调用之前
• 增强次数?只增强一次
import time

"""
通过装饰器进行函数增强,只是一种语法糖,本质上跟断码段3完全一致.
"""


def count_time_wrapper(func):
    """
    闭包,用于增强函数func: 给函数func增加统计时间的功能
    """

    def improved_func():
        start_time = time.perf_counter()  # 起始时间
        func()  # 执行函数
        end_time = time.perf_counter()  # 结束时间
        print(
            "it takes {} s to find all the olds".format(end_time - start_time))

    return improved_func


@count_time_wrapper
def print_odds():
    """
    输出0~100之间所有奇数,并统计函数执行时间
    """
    for i in range(100):
        if i % 2 == 1:
            print(i)


if __name__ == '__main__':
    # 装饰器等价于在第一次调用函数时执行以下语句:
    # print_odds = count_time_wrapper(print_odds)
    print_odds()

 

装饰器的多种情况:

1、主函数无参数列表和返回值

即代码段4

2、主函数有返回值

代码段5:

import time


def count_time_wrapper(func):
    """
    闭包,用于增强函数func: 给函数func增加统计时间的功能
    """

    def improved_func():
        start_time = time.perf_counter()   # 起始时间
        func()                      # 执行函数
        end_time = time.perf_counter()     # 结束时间
        print("it takes {} s to find all the olds".format(end_time - start_time))


    return improved_func


def count_odds():
    """
    输出0~lim之间所有奇数,并统计函数执行时间
    """
    cnt = 0
    for i in range(100):
        if i % 2 == 1:
            cnt+=1
    return cnt


# 对于含有返回值的函数,调用闭包增强后,不能成功返回,但是成功增强了辅助功能
if __name__ == '__main__':
    print('增强前')
    print(count_odds())         # 装饰前函数能正常返回
    print('----------------------')
    print('增强后')
    count_odds = count_time_wrapper(count_odds)
    print(count_odds())         # 装饰后函数不能正常返回

结果:

增强前
50
----------------------
增强后
it takes 9.200000000000874e-06 s to find all the olds
None

所以对于有返回值的主函数,我们需要在闭包函数中将返回值再次返回出来,将代码段5进行修改:

代码段6:

import time


def count_time_wrapper(func):
    """
    闭包,用于增强函数func: 给函数func增加统计时间的功能
    """

    def improved_func():
        start_time = time.perf_counter()   # 起始时间
        result = func()                      # 执行函数
        end_time = time.perf_counter()     # 结束时间
        print("it takes {} s to find all the olds".format(end_time - start_time))
        return result
    return improved_func


def count_odds():
    """
    输出0~lim之间所有奇数,并统计函数执行时间
    """
    cnt = 0
    for i in range(100):
        if i % 2 == 1:
            cnt+=1
    return cnt


# 对于含有返回值的函数,调用闭包增强后,不能成功返回,但是成功增强了辅助功能
if __name__ == '__main__':
    print('增强前')
    print(count_odds())         # 装饰前函数能正常返回
    print('----------------------')
    print('增强后')
    count_odds = count_time_wrapper(count_odds)
    print(count_odds())         # 装饰后函数能正常返回

结果:

增强前
50
----------------------
增强后
it takes 9.100000000001468e-06 s to find all the olds
50

3、主函数有参数列表:在代码段6的主函数加上一个参数,如代码段7所示:

import time


def count_time_wrapper(func):
    """
    闭包,用于增强函数func: 给函数func增加统计时间的功能
    """

    def improved_func():
        start_time = time.perf_counter()   # 起始时间
        result = func()                      # 执行函数
        end_time = time.perf_counter()     # 结束时间
        print("it takes {} s to find all the olds".format(end_time - start_time))
        return result
    return improved_func


def count_odds(lim=100):
    """
    输出0~lim之间所有奇数,并统计函数执行时间
    """
    cnt = 0
    for i in range(lim):
        if i % 2 == 1:
            cnt+=1
    return cnt


# 对于含有参数的函数,调用闭包增强后,不能成功接收参数
if __name__ == '__main__': print('增强前') print(count_odds(lim=1000)) # 装饰前函数能正常运行 print('----------------------') print('增强后') count_odds = count_time_wrapper(count_odds) print(count_odds(lim=1000)) # 装饰后函数不能正常运行

运行结果:运行失败,提示缺少参数

TypeError: improved_func() got an unexpected keyword argument 'lim'
增强前 500 ---------------------- 增强后

将代码段7进行修改:

import time


def count_time_wrapper(func):
    """
    闭包,用于增强函数func: 给函数func增加统计时间的功能
    """

    def improved_func(*args, **kwargs):     # 增强函数应该把接收到的所有参数传给原函数
        start_time = time.perf_counter()   # 起始时间
        ret = func(*args, **kwargs)                     # 执行函数
        end_time = time.perf_counter()     # 结束时间
        print("it takes {} s to find all the olds".format(end_time - start_time))
        return ret      # 增强函数的返回值应该是原函数的返回值

    return improved_func


def count_odds(lim=100):
    """
    输出0~100之间所有奇数,并统计函数执行时间
    """
    cnt = 0
    for i in range(lim):
        if i % 2 == 1:
            cnt += 1
    return cnt


if __name__ == '__main__':
    print('增强前')
    print(count_odds(lim=100000))  # 装饰前函数能正常返回,能接收参数
    print('-----------')
    print('增强后')
    count_odds = count_time_wrapper(count_odds)
    print(count_odds(lim=100000))  # 装饰后函数能正常返回,能接收参数

运行结果:

增强前
50000
-----------
增强后
it takes 0.004931700000000004 s to find all the olds
50000
一个保留了参数列表和返回值的函数闭包写法:

 

 

 关于装饰器的面试题:

1、多个装饰器,求输出

题目:

def wrapper1(func1):
    print('set func1')  # 在wrapper1装饰函数时输出

    def improved_func1():
        print('call func1')  # 在wrapper1装饰过的函数被调用时输出
        func1()

    return improved_func1


def wrapper2(func2):
    print('set func2')  # 在wrapper2装饰函数时被输出

    def improved_func2():
        print('call func2')  # 在wrapper2装饰过的函数被调用时输出
        func2()

    return improved_func2


@wrapper1
@wrapper2
def original_func():
    pass


if __name__ == '__main__':
    original_func()
    print('-----')
    original_func()

结果:

set func2
set func1
call func1
call func2
-----
call func1
call func2

详细解释:

def wrapper1(func1):
    print('set func1')

    def improved_func1():
        print('call func1')
        func1()     # 封装了improve_func2(original_func)

    return improved_func1


def wrapper2(func2):
    print('set func2')

    def improved_func2():
        print('call func2')
        func2()     # 封装了original_func

    return improved_func2


# @wrapper1
# @wrapper2
def original_func():
    pass


if __name__ == '__main__':
    # 两个装饰器等价形式1:
    # original_func = wrapper1(wrapper2(original_func))
    # 两个装饰器等价形式2:
    print(original_func.__name__)               # 未进行封装前:original_func = original_func()
    original_func = wrapper2(original_func)
    print(original_func.__name__)               # original_func = improved_func2(封装original_func)()
    original_func = wrapper1(original_func)
    print(original_func.__name__)               # original_func = improved_func1(improved_func2(封装original_func))()

    # 此时的original_func = improved_func1(improve_func2(original_func))
    original_func()
    print('-----')
    original_func()

1、流程:

func = warp1(warp2(func))
封装顺序:warp2--warp1
封装流程:warp1封装的内容是imporved2, imporved2是func经warp2封装过的一级封装 
调用顺序:imporve1---imporve2

 

 

 2、为什么第一次执行original_func()和第二次执行original_func()输出不一样?

装饰器在第一次调用被装饰函数时进行增强
• 增强时机?在第一次调用之前
• 增强次数?只增强一次

 2、如何创建带参数的装饰器

在装饰器中填写info字段

 
posted @ 2021-12-27 23:58  钟胜一  阅读(64)  评论(0编辑  收藏  举报