首页 > 其他分享 >补充 : 函数之装饰器详解

补充 : 函数之装饰器详解

时间:2023-06-01 16:44:06浏览次数:41  
标签:函数 详解 func time print 执行 装饰 def

函数之装饰器详解

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

【一】简单版本的装饰器

# -*-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内部加上其他的多余的函数和功能
    # 这就是装饰器
    
    

标签:函数,详解,func,time,print,执行,装饰,def
From: https://www.cnblogs.com/dream-ze/p/17449506.html

相关文章

  • mysql聚合函数---总体聚合、总体累加、分组聚合、分组累加
    MySQL从版本8.0开始,才支持窗口函数,所以之前的版本分组累加需要构造sql语句来实现。数据:select*fromemp;一、mysql总体聚合函数min()、max()、count()、sum()、avg()selectcount(ename),max(sal),min(sal),sum(sal),round(avg(sal),2)fromemp;二、mysql总体累加/总体累计数量......
  • 去往js函数式编程(5)
    日志记录  我们可以写一个高阶函数,它以两个函数作为参数,并允许第一个函数只能执行一次,从那一点开始调用第二个函数。constonceAndAfter=(f,g)=>{lettoCall=freturn(...args)=>{letresult=toCall(...args)toCall=greturnresult}}......
  • 支持向量机(含具体推导、核函数)
    参考了西瓜书,《机器学习》周志华背景在超平面(比如三维立体,甚至更高维)上,找到一个分类面\[\boldsymbol{w}^T\boldsymbol{x}+b=0\]看起来很陌生,其实直线方程和\(Ax+By+C=0\)一个道理,只不过拓展到了高维,另外注意这里的\(\boldsymbol{x}\)是指一个高维变量使得分......
  • slice()函数创建一个slice对象
    slice()函数创建一个slice对象,该对象可用于对字符串,列表,元组等进行切片。slice对象用于切片给定的序列(字符串,字节,元组,列表或范围)或任何支持序列协议的对象(实现__getitem__()和__len__()方法)。slice语法:classslice(stop)classslice(start,stop[,step])my_slice=slice(5)......
  • 高阶函数处理字符串方法
    1、concat()用于将一个或多个字符串拼接成一个新字符串。来看下面的例子:letstringValue="hello";letresult=stringValue.concat("world");//可接收任意多个参数letres=stringValue.concat("world","!!");console.log(result);//"helloworl......
  • z函数|exkmp|拓展kmp 笔记+图解
    题外话,我找个什么时间把kmp也加一下图解z函数|exkmp别担心这个exkmp和kmp没毛点关系,请放心食用。本文下标以1开始,为什么?因为1开始就不需要进行长度和下标的转换,长度即下标。定义给出模板串S和子串T,长度分别为n和m,对于每个ans[i](1<=i<=n),求出S[i...n]与T的最长公共前缀长......
  • Mybatis的五种分页方式详解
     第一种:LIMIT关键字1,mapper代码select*fromtb_userlimit#{pageNo},#{pageSize}2,业务层直接调用publicListfindByPageInfo(PageInfoinfo){returnuserMapper.selectByPageInfo(info);}3,优点灵活性高,可优化空间大mysql分页语句优化4,缺点实现复杂。 第......
  • SQL改写案例6(开窗函数取中位数案例)
    周总找我问个报表SQL实现逻辑的案例,废话不说给他看看。 原SQL:SELECTd.tname姓名,d.spname岗位,d.sum_cnt报单单量,d.min_cnt放款单量,d.date月份FROM(SELECT*FROM(SELECTa.zts_name......
  • c++ const详解
    可以使用const的地方就尽量使用const一般引用的类型必须与其所引用对象的类型一致,但是允许一个常量引用绑定到非常量的对象、字面值,甚至是一个一般表达式doublea=42.0;int&b=a;//编译错误,一般引用类型需要与所引用对象类型一致inta=42;constint&b=a;a=......
  • java中函数式编程的一些测试
    目录Java中函数式编程的一些测试树反转数据处理科里化Optional函数组合全部代码Java中函数式编程的一些测试在上一篇文章中,提及了java中的函数式编程,但缺少了一些相关的代码,这里补充一下.注意,本文中的代码并不代表最佳实践,只是提供一种思路,其中有很多代码并没有实......