装饰器(重点)

一、装饰器介绍

1.1 为何要用装饰器

软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。

1.2 什么是装饰器

‘装饰’代指为被装饰对象添加新的功能,‘器’代指器具/工具。装饰器的核心思想是在不改变被装饰对象"内部代码"和"原有调用方式"的基础之上添加额外的功能。

并且,装饰器不是一个新的知识点,它是我们之前学习的:名称空间,函数对象,闭包函数组合而来

二、装饰器的实现

时间模块

# 补充知识点:
import time  # 时间模块
'''返回的结果是:从代码运行的那一刻到1970-1-1的秒数'''
print(time.time())  # 时间戳  # 1677557680.7924008  

time.sleep(3)  # 代码就会停止3秒
print('睡好了')

2.1 无参装饰器的推导流程

如果想为下述函数添加统计其执行时间的功能

import time

def index():
    time.sleep(2)
    print('from index')

index()  # 函数执行

遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样

1.直接在调用index函数的前后添加代码

# 1. 在函数执行之前,要记录一下此时的时间戳
start_time = time.time()
# 2. 开始执行函数
index()
# 3. 函数执行完毕之后,再次记录此时的时间戳
end_time = time.time()
# 4. 统计函数的执行时间
print('执行了%s秒' % (end_time - start_time))

如果想要为另一个函数添加统计执行时间的功能,就需要重复上面四个步骤。如果要调用的地方比较多,就需要多次拷贝代码。一个简单的功能写了多行代码,不够简便。

相同的代码需要在不同的位置反复执行>>>可以想到函数

2.把统计执行时间做成函数形式

def get_time():
    # 统计函数的执行时间
    start_time = time.time()
    index()
    end_time = time.time()
    print('执行了%s秒' % (end_time - start_time))

以上把函数体代码写死了,只能够执行index函数的执行时间,如何才能做到统计更多的函数运行的时间,可以直接传参变换统计的函数

3.传参

def get_time(func_name):
    # 统计函数的执行时间
    start_time = time.time()
    func_name()
    end_time = time.time()
    print('执行了%s秒' % (end_time - start_time))
    
get_time(index)
# 想要调用home,把实参改成home即可
get_time(home)

虽然实现了一定的兼容性,但是并不符合装饰器的特征,第一种传参不写,>>>>>>只能考虑闭包

4.考虑用闭包函数传参

def outer(func_name):
    # func_name = index  # 写死了,想要写活放到形参位置
    def get_time():
        # 统计函数的执行时间
        start_time = time.time()
        func_name()
        end_time = time.time()
        print('函数的执行时间为>>>:', end_time - start_time)
    return get_time
res = outer(index)
res()

res1 = outer(home)
res1()

以上还是改变了调用方式,想着如何变形呢?

5.变量名赋值绑定(******)

def outer(func_name):
    # func_name = index  # 写死了,想要写活放到形参位置
    def get_time():
        # 统计函数的执行时间
        start_time = time.time()
        func_name()
        end_time = time.time()
        print('函数的执行时间为>>>:', end_time - start_time)
    return get_time
# res = outer(index)  # 赋值符号的左边是一个变量名,可以随意的命名
# res1 = outer(index)
# jason = outer(index)
index = outer(index)  # get_time的内存地址
index()  # <=>get_time()

至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常

2.2 装饰器解决参数问题

6.上述装饰器只能装饰无参函数,兼容性太差

def index(a):
    time.sleep(0.1)
    print('from index', a)

def func1(a, b):
    time.sleep(0.2)
    print('from func1, %s:%s' % (a, b))

def func2():
    time.sleep(0.3)
    print('from func2')

def outer(func_name):
    # func_name = index
    def get_time():
        # 统计函数的执行时间
        start_time = time.time()
        func_name()
        end_time = time.time()
        print('执行了%s秒' % (end_time - start_time))
    return get_time
# index = outer(index)  # get_time的内存地址
# index(1)  
# func1 = outer(func1)
# func1(1, 2)
# func2 = outer(func2)
# func2()
# 这样输出错误,此时index实际是get_time,而get_time是无参函数
# 应该在func_name函数要加上参数,它又在内部,该怎么加呢?

被装饰的函数不知道有没有参数以及有几个参数,如何兼容,使用可变长参数

7.*args, **kwargs

def index(a):
    time.sleep(0.1)
    print('from index', a)

def func1(a, b):
    time.sleep(0.2)
    print('from func1: %s,%s' % (a, b))

def func2(a, b, c):
    time.sleep(0.3)
    print('from func2', a, b, c)

def outer(func_name):
    # func_name = index
    def get_time(*args, **kwargs):
        # 统计函数的执行时间
        start_time = time.time()
        func_name(*args, **kwargs)
        end_time = time.time()
        print('执行了%s秒' % (end_time - start_time))
    return get_time
index = outer(index)  # get_time的内存地址
index(1)
func1 = outer(func1)
func1(1, 2)
func2 = outer(func2)
func2(1, 4, 5)

2.3 解决返回值问题

8.如果被装饰的函数有返回值

import time

'''8.如果被装饰的函数有返回值'''
def index():
    time.sleep(3)
    print('from index')
    return 'hello index'

def home(a, b):
    time.sleep(2)
    print('from home:%s,%s' % (a, b))
    return 'home'

def outer(func_name):
    def get_time(*args, **kwargs):
        # 统计函数的执行时间
        start_time = time.time()
        res = func_name(*args, **kwargs)  # 用变量来接收index(),也就是index的返回值
        end_time = time.time()
        print('执行了%s秒' % (end_time - start_time))
        return res  # 才是真正的函数的返回结果
    return get_time
# index = outer(index)
# res = index()
# print(res)  # None

home=outer(home)
home(1, 2)
print(home(1, 2))  # home

2.4 课堂练习题

写一个认证装饰器:
定义一个index函数,让用户输入用户名和密码,如果输入正确,就执行index函数,否则不能执行函数

2.4.1 认证装饰器

def index():
    print('from index')

def home():
    print('from home')

def login_auth(func_name):
    # func_name = index
    def auth():
        # 1.让用户输入用户名和密码
        username = input('username:>>>').strip()
        password = input('password:>>>').strip()

        # 2.认证,判断用户名和密码是否正确
        if username == 'kevin' and password == '123':
            func_name()
        else:
            print('认证失败,不能执行函数')
    return auth
# index = login_auth(index)  # auth的内存地址
# index()  # auth()
home = login_auth(home)
home()

2.4.2 认证升华

如果有一个函数被认证成功,后续的函数都不在认证了

def index():
    print('from index')

def home():
    print('from home')

def transfer():
    print('转账功能')


# 定义一个变量来存储是否验证成功
is_login = {'is_login': False}
# is_login = False

def login_auth(func_name):
    # func_name = index
    def auth():
        # global is_login
        # if is_login:
        if is_login.get('is_login'):
            res = func_name()
            return res
        # 1.让用户输入用户名和密码
        username = input('username:>>>').strip()
        password = input('password:>>>').strip()

        # 2.认证,判断用户名和密码是否正确
        if username == 'kevin' and password == '123':
            res = func_name()
            is_login['is_login'] = True
            # res = func_name()
            return res
        else:
            print('认证失败,不能执行函数')
    return auth
index = login_auth(index)  # auth的内存地址
index()  # auth()
home = login_auth(home)
home()
transfer = login_auth(transfer)
transfer()

三、装饰器的固定模板

def outer(func):
    def inner(*args, **kwargs):
        print('在函数执行之前需要添加的功能')
        res=func(*args, **kwargs)
        print('在函数执行之后需要添加的功能')
        return res
    return inner

四、装饰器的语法糖

def outer(func):
    def inner(*args, **kwargs):
        print('在函数执行之前需要添加的功能')
        res=func(*args, **kwargs)
        print('在函数执行之后需要添加的功能')
        return res
    return inner

@outer  # index=outer(index)
def index():
    print('from index')
index()

@outer  # home=outer(home)
def home():
    print('from home')
home()


"""
专业语法:语法糖
    装饰器的本质就是函数的执行
    书写规范:
        1. 语法糖要写在被装饰对象的头上
        2. 原理:把紧贴着的函数名当作第一个参数自动传给@后面的函数调用
"""

五、双层装饰器

import time

def outer(func_name):
    # func_name = index
    def get_time(*args, **kwargs):
        # 1. 在函数执行之前,要记录一下此时的时间
        start_time = time.time()
        # 2. 开始执行函数
        func_name(*args, **kwargs)
        # 3. 函数执行完毕之后,再次记录此时的时间
        end_time = time.time()
        # 4. 统计函数的执行时间
        print('执行了%s秒' % (end_time - start_time))
    return get_time


def login_auth(func_name):
    # func_name = index
    def auth():
        # 1. 让用户输入用户名和密码
        username = input('username:').strip()
        password = input('password:').strip()

        # 2. 要认证,判断用户名和密码是否正确
        if username == 'kevin' and password == '123':
            # 才是正常执行的函数
            res=func_name()
            return res
        else:
            print('认证失败,不能执行函数')
    return auth

@login_auth # index=login_auth(get_time)
@outer  # get_time=outer(index)
def index():
    time.sleep(1)
    print('from index')

index()  # auth()
'''
双层语法糖  加载顺序由下往上
每次执行之后如果上面还有语法糖,则直接将返回值函数名传递给上面的语法糖,如果上面没有语法糖了,则变为  index = login_auth(get_time)
'''

六、装饰器修复技术

6.1 help

可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释

print(help(index))

'''
输出结果是:
Help on function auth in module __main__:

auth()
    # func_name = index
'''

6.2 functools模块

使用修复技术后被装饰对象就不容易查看了,但仍然不会改变index函数已经被装饰的结果,使用语句是:
from functools import wraps
@wraps(参数) # 此语句是写到外部函数中,参数是外部函数的参数(也就是被装饰对象的形参)

from functools import wraps
def login_auth(func_name):
    # func_name = index
    @wraps(func_name)  # 修复技术是为了让被装饰对象不容易被察觉装饰了
    def auth():
        # 1. 让用户输入用户名和密码
        username = input('username:').strip()
        password = input('password:').strip()

        # 2. 要认证,判断用户名和密码是否正确
        if username == 'kevin' and password == '123':
            # 才是正常执行的函数
            res=func_name()
            return res
        else:
            print('认证失败,不能执行函数')
    return auth

print(help(index))
'''
输出结果是:
Help on function index in module __main__:

index()
    # func_name = index
'''

七、三层装饰器

# 判断七句print执行顺序
def outter1(func1):
    print('加载了outter1')               # 3
    def wrapper1(*args, **kwargs):
        print('执行了wrapper1')          # 4
        res1 = func1(*args, **kwargs)
        print('hello1')                 # 10
        return res1
    return wrapper1

def outter2(func2):
    print('加载了outter2')               # 2
    def wrapper2(*args, **kwargs):
        print('执行了wrapper2')          # 5
        res2 = func2(*args, **kwargs)
        print('hello2')                 # 9
        return res2
    return wrapper2

def outter3(func3):
    print('加载了outter3')                # 1
    def wrapper3(*args, **kwargs):
        print('执行了wrapper3')           # 6
        res3 = func3(*args, **kwargs)
        print('hello3')                  # 8
        return res3
    return wrapper3


@outter1  # index=wrapper1=outter1(wrapper2)
@outter2  # wrapper2=outter2(wrapper3)
@outter3  # wrapper3=outter3(index)
def index():
    print('from index')                   # 7

index()

# 输出结果是:
# 加载了outter3
# 加载了outter2
# 加载了outter1
# 执行了wrapper1
# 执行了wrapper2
# 执行了wrapper3
# from index
# hello3
# hello2
# hello1

八、有参装饰器

当装饰器中需要额外的参数时>>>:有参装饰器

函数auth需要一个source_data参数,而函数login_auth与auth的参数都有其特定的功能,不能用来接受其他类别的参数,可以在login_auth的外部再包一层函数outter,用来专门接受额外的参数,这样便保证了在outter函数内无论多少层都可以引用到

def outter(source_data,a,b,c,d,e,f, *args, **kwargs):
    # source_data='file'
    def login_auth(func_name):
        # func_name = index
        def auth(*args, **kwargs):
            # 1. 让用户输入用户名和密码
            username = input('username:').strip()
            password = input('password:').strip()
    
            # 2. 要认证,判断用户名和密码是否正确
            if source_data == 'file':
                print('数据来源是文件')
            elif source_data=='mysql':
                print('数据来源是MySQL')
            elif source_data=='oracle':
                print('数据来源是oracle')
            else:
                print('认证失败,不能执行函数')
                
        return auth
    return login_auth

此时我们就实现了一个有参装饰器,使用方式如下

# 先调用outter('source_data'='file'),得到@login_auth,@login_auth的语法意义与无参装饰器一样

@outter('file') # @login_auth
def index():
    print('from index')

# 如果有多个参数,也可以一起放在outter中
@outter('file',1,2,3,4,6,7,8) # @login_auth
def index():
    print('from index')
    
'''当装饰器中需要额外的参数时>>>:有参装饰器'''

"""
函数名加括号执行优先级最高 有参装饰器的情况 
    先看函数名加括号的执行
    然后再是语法糖的操作
"""

posted @ 2023-03-01 19:35  星空看海  阅读(25)  评论(0编辑  收藏  举报