补充 : 函数之装饰器详解

函数之装饰器详解

装饰器的用途就是为了在不改变原来代码的前提下,将新的功能和函数加入进去

【一】简单版本的装饰器

# -*-coding: Utf-8 -*-
# @File : 装饰器详解 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/5/20

import time


def func_1():
    # 让程序睡 3s 方便计时
    time.sleep(3)
    print('这是一个执行函数!')

    # 需求:我们想在不改变原来代码的前提下将一个计时的功能加进去。让计时功能记录函数执行的时间
    # 于是我们就有了思路:我们先在程序执行前记录一个时间,再在程序执行后记录一个执行时间
    # 当程序运行完成时,我们用执行完成后的时间 - 执行程序之前的时间
    # 如下   

# 记录执行前的时间
start_time = time.time()
# 执行函数
func_1()
# 记录执行后的时间
end_time = time.time()

# 统计时间
print(f'总耗时为{end_time - start_time}')

# 这是一个执行函数!
# 总耗时为3.113999843597412

【进阶一】进阶版本的函数装饰器

  • 上面的方法存在一个很致命的弊端
  • 我们每次统计的函数执行时间的函数名是固定的,如果我们换个函数就不能了
  • 因此我们 对其做了一个小优化,将其封装成函数,把要执行的函数名作为参数传进去
# -*-coding: Utf-8 -*-
# @File : 装饰器详解 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/5/20

import time


def func_1():
    # 让程序睡 3s 方便计时
    time.sleep(3)
    print('这是一个执行函数!')

    # 需求:我们想在不改变原来代码的前提下将一个计时的功能加进去。让计时功能记录函数执行的时间
    # 于是我们就有了思路:我们先在程序执行前记录一个时间,再在程序执行后记录一个执行时间
    # 为了解决每次函数调用名只能固定的问题,我们在内部再定义一个函数,将外部的函数名作为参数传进去,而不是将整个函数传进去,我们也办不到
    # 当程序运行完成时,我们用执行完成后的时间 - 执行程序之前的时间
    # 如下


# 我们将函数名作为实参传进去
def main(func_name):
    def get_time(*args, **kwargs):
        # 记录执行前的时间
        start_time = time.time()
        # 这里的函数名用一个形参代替,传进来的函数名是什么,他就会调用这个函数进行执行
        func_name(*args, **kwargs)
        # 记录执行后的时间
        end_time = time.time()
        # 统计时间
        print(f'总耗时为{end_time - start_time}')
 	return get_time

get_time = main(func_1)
get_time()

# 这是一个执行函数!
# 总耗时为3.0046579837799072

【进阶二】解决装饰器内部的函数参数的问题

  • 虽然我们解决了内部函数名只能是定值的问题,现在我们将我们想要运行的函数名传进去即可运行程序执行相应的功能

  • 但是,我们都知道,函数作为简化代码的重要手段,其中最重要的一环就是向函数内部进行传值,传到对应函数里面进行调用执行

这时,我们可以想到,我们前面函数参数学到的两个方法

一个是 *args 这个形参的意思是,将多余的参数以数组(1,2,3,)传进去

在函数内部的真正函数里面。同样也有一种方法,就是将这个元祖打散,将元祖里面的参数一个个的传给参数

另一个 **kwargs 这个形参的意思是,可以以键值对的形式将参数传进去

当在函数内部调用的时候,这个方法会将键值对打散成一对对键值对传进去,利用这个机制,我们可以对内部的函数进行传参

# -*-coding: Utf-8 -*-
# @File : 装饰器详解 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/5/20

import time


def func_1(a, b):
    # 让程序睡 3s 方便计时
    time.sleep(3)
    print(f'这是我要接收的参数{a}和{b}')
    print('这是一个执行函数!')

    # 需求:我们想在不改变原来代码的前提下将一个计时的功能加进去。让计时功能记录函数执行的时间
    # 于是我们就有了思路:我们先在程序执行前记录一个时间,再在程序执行后记录一个执行时间
    # 为了解决每次函数调用名只能固定的问题,我们在内部再定义一个函数,将外部的函数名作为参数传进去,而不是将整个函数传进去,我们也办不到
    # 当程序运行完成时,我们用执行完成后的时间 - 执行程序之前的时间
    # 如下


# 我们将函数名作为实参传进去
def main(func_name):
    def get_time(*args, **kwargs):
        # 记录执行前的时间
        start_time = time.time()
        # 这里的函数名用一个形参代替,传进来的函数名是什么,他就会调用这个函数进行执行
        func_name(*args, **kwargs)
        # 记录执行后的时间
        end_time = time.time()
        # 统计时间
        print(f'总耗时为{end_time - start_time}')

    # 这里返回的是内部函数的内存空间地址,我们可以在外部查看这个返回值
    return get_time  # <function main.<locals>.get_time at 0x0000021E0025B4C0>


get_time = main(func_1)
get_time('你好', 12)


# 这是我要接收的参数你好和12
# 这是一个执行函数!
# 总耗时为3.0047993659973145

  • 从这个函数的调用结果里我们可以看到,在内部的函数接收到了我们传进去的参数

【进阶三】解决返回值的问题

  • 我们都知道,我们在调用函数时,返回值是我们重要的一环,我们可以根据返回的信息做下一步函数的调用
  • 在装饰内部,我们则可以采用将返回值再次返回的方法进行返回下一步等待调用
比如说我们有一个函数 login 
登陆成功后会返回 True
登陆失败后会返回 False

我们从返回值取返回值进行二次判断

简单原理就是
def shopping():
    theOrder_list = ['薯片', '辣条', '可乐']
    print('进入购物系统成功,返回订单')
    return theOrder_list


def outer(func_name):
    def login(*args, **kwargs):
        username = input('enter your username')
        password = input('enter your password')
        if username == '123' and password == '1230':
            print('你好,登陆成功!')
            response = func_name(*args, **kwargs)
            return response
        else:
            print('请重新输入信息!')

    return login


login = outer(shopping)
login()
# enter your username123
# enter your password1234
# 请重新输入信息!
# 进入购物系统成功,返回订单
# ['薯片', '辣条', '可乐']



在内部的return方法就类似于下面


response = shopping()
print(response)

# 进入购物系统成功,返回订单
# ['薯片', '辣条', '可乐']

【进阶四】内部的逻辑认证

  • 我们现在有了一个新的需求
  • 我们需要做一个认证装饰器
  • 定义一个 shopping 函数,让用户输入用户名和密码,如果输入正确,就执行 shopping 函数,得到零食列表,否则不能执行函数
def shopping():
    theOrder_list = ['薯片', '辣条', '可乐']
    print('进入购物系统成功,返回订单')
    return theOrder_list


def outer(func_name):
    def login(*args, **kwargs):
        username = input('enter your username')
        password = input('enter your password')
        if username == '123' and password == '1230':
            print('你好,登陆成功!')
            response = func_name(*args, **kwargs)
            return response
        else:
            print('请重新输入信息!')

    return login


login = outer(shopping)
login()
# enter your username123
# enter your password1234
# 请重新输入信息!
# 进入购物系统成功,返回订单
# ['薯片', '辣条', '可乐']

  • 我们再在这个基础上新加入一个新功能
  • 我们假设还有其他待执行的函数
    • 现在如果我们进入某个功能时,都会先进行身份验证
  • 但是我已经认证成功了,我不想再每次都认证,这样我很烦
  • 我们需要实现的功能就是,当我认证成功一次过后,我就不用再认证而是直接进行其他操作
# -*-coding: Utf-8 -*-
# @File : 装饰器详解 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/5/20


def shopping():
    theOrder_list = ['薯片', '辣条', '可乐']
    print('进入购物系统成功,返回订单')
    return theOrder_list

# 定义一个字典 : 我们都知道字典是可变数据类型,可以通过外部进行改变
# 如果是一个字符串,我们无法改变他的内存空间里面的值
chose = {'is_True': False}


# 这个是装饰器函数
def outer(func_name):
    # 这个是内部被装饰的函数
    def login(*args, **kwargs):
        while True:
            if chose['is_True']:
                # 这是登录前的函数(登陆不成功则每次都会调用这个函数)
                response = response = func_name(*args, **kwargs)
                return response

            username = input('enter your username')
            password = input('enter your password')
            if username == '123' and password == '1230':
                print('你好,登陆成功!')
                # 登陆成功后将外部的方法重置 将外部的认证函数默认值改为False 不去再走默认的登陆操作
                chose['is_True'] = False
                # 登陆成功后会执行的函数部分
                response = func_name(*args, **kwargs)
                return response
            else:
                # 用户名和密码认证失败则进行校对操作
                print('请重新校验用户名和密码!')


    return login


login = outer(shopping)
login()
# enter your username123
# enter your password1234
# 请重新输入信息!
# 进入购物系统成功,返回订单
# ['薯片', '辣条', '可乐']

【五】装饰器的固定模板

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
def home():
    print('from home')

home()

【八】个人总结 - 装饰器

装饰器本质上是一个函数。通过接收原始函数,装饰器可以在原始函数执行添加业务逻辑,使用装饰器的好处可以不污染原始函数。

我可以先定义一个最外部的函数 outer 名,这个函数名有一个参数 foo_name ,参数是需要传进去的等待调用的那个函数名;外部也就是 outer(foo_name)

  • 然后我在这个函数的内部再定义一个函数 inner ,并将这个函数的内存地址返回也就是不加()的函数名。

  • 我将这个函数调用地址返回,那我外部的函数需要一个参数,这个参数就是需要调用的函数名,我把这个真正需要接受函数名的函数再写在内部的这个形参调用的位置,将他传入的函数名接入进来

  • 也就是

    def right():
        print('真正需要执行的函数')
    
    # 外部的函数传入形参
    def outer(foo_name):
        def inner()
        	# 这是真正需要执行的内部函数,接收传进来的函数名,进行真正的调用外部的函数
        	foo_name()
            
            
         # 这里返回的是inner的内存地址   
        return inner
    
    # 在外部调用内部函数
    # 需要先将这个调用地址赋值给一个变量名 也就是,先调用函数传进去真正需要执行的函数名 outer(right)
    # 此时的返回值是内部函数的内存空间地址 
    # 因为装饰器遵从不改变原来函数的基础上加其他的方法 那我们就用原来的函数名去 命名这块内存空间地址 right = outer(right)
    # 因为内存空间地址就是给我们指明了这个函数存在的地方,那我们理所当然的能用这个变量名去调这块内存空间中的函数 也就是 inner()
    
    # 所以完整的调用过程就是
    right = outer(right)
    right()
    
    # 在没有改变外部函数的功能和前提的条件下,我还能在 inner内部加上其他的多余的函数和功能
    # 这就是装饰器
    
    
posted @ 2023-06-01 16:40  Chimengmeng  阅读(47)  评论(0编辑  收藏  举报