Loading

闭包函数、装饰器大全

今日内容概要

  • 闭包函数(重要)
  • 闭包函数的实际应用
  • 装饰器简介(重点+难点)
  • 简易版本装饰器
  • 进阶版本装饰器
  • 完整版本装饰器
  • 装饰器模板(拷贝使用即可)
  • 装饰器语法糖
  • 装饰器修复技术

img

内容详细

闭包函数

1.必须嵌套函数。

2.内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量

3.外部函数必须返回内嵌函数——必须返回那个内部函数

闭包函数的两大特征:

定义在函数内部的函数
内部函数使用了外层函数名称空间中的名字

使用eg:

def outer():
    x = 888
    def inner():
        print('from outer>>>inner',x)
    return inner
x = 666
res = outer()
res()

闭包函数实际应用

闭包函数是给函数体传参的另外一种方式

# 函数体传参的方式1:形参
def index(username):
    print(username)
函数体代码需要什么就可以在形参中写什么
index('tuzi')  # 但这样只能写成一个参数,相当于写死了
# 函数体传参的方式2:闭包
def index(username):
    print(username)
index('jason')
def outer(username):
    # username = 'jason'  # 使用的永远都是jason
    def index():
        print(username)  #
    return index
res = outer('tuzi')   # 将参数'tuzi'传入username里面
res()
res1 = outer('shazi')  # 形参username与值'shazi'临时绑定>>>:outer局部空间中
res1()

装饰器简介

'''
就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名的情况下,给被装饰对象添加新的功能 。函数装饰器有开发封闭原则;开放是对扩展开放,封闭是对修改封闭。函数装饰器必须遵守的原则:一是不改变被装饰对象的源代码,二是不改变装饰对象的调用方式。
'''
# 本质就是:在不改变被装饰对象原有的'调用方式'和'内部代码'的情况下给被装饰对象添加新的功能
装饰器的原则:对扩展开放、对修改封闭    

我们可以做一个需求:

# 需求:统一函数的执行时间
import time  # 时间戳
print(time.time())
time.sleep(3)
print('helloworld')
# ----------------------------------------------------
import time
def index():
    time.sleep(3)
    print('form index')
start_time  = time.time()  # 函数执行之前获取一个时间戳
index()
end_time = time.time()  # 函数执行之后获取一个小时时间戳
print(end_time - start_time)  # 两个时间戳的差值就是函数的执行时间
# 但直接传参的方式有几个缺陷
缺陷1:代码写死了 无法统计其他函数的执行时间
		解决方法:将函数名通过形参的形式传入
缺陷2:封装成函数之后 调用方式改变了 不符合装饰器原则
            不可以解决

简易版本装饰器

简易版装饰器
def outer(func_name):  # 1. 定义外部函数
    def inner():  # 3.定义内部函数
        start_time = time.time()  # 7. 获取函数运行前的时间戳
        func_name()  # 8. 调用函数,内部函数调用外部函数绑定的形参,由于第2步形参传值,func_name与index临时绑定到一起,且这里是先调用outer函数再赋值,所以func_name绑定的是上面的函数名index,即调用函数index()
        end_time = time.time()  # 9. 获取函数运行后的时间戳
        print(end_time - start_time)  # 输出函数运行前后时间戳的差值
    return inner  # 4. 把内部函数的函数名返回出去
index = outer(index)  # 2. 调用外部函数  # 5. 定义变量名index,让index指向outer的返回值
index()  # 6. 调用函数,由于index指向inner,所以index()等价于inner(),即调用函数inner()
'''index = outer(index) 相当于狸猫换太子, 看着是原来的,但内部不是原来的index了'''

fef22230ef8568467d9796f79fd1ae0

进阶版本装饰器

# 解决参数的问题
def outer(func_name):
    def get_time(*args, **kwargs):  # 用来接收全局空间传过来的参数,将这些参数全部传给func_name的形参里
        start_time = time.time()
        func_name(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
    return get_time
这里如果再调用函数的话,不管函数里面有没有参数,都可以运行上面这段代码,也就很好的解决了函数传参的问题

完整版本装饰器

# 解决的是返回值问题
def outer(func_name):
    def get_time(*args,**kwargs): # 用来接收全局空间传过来的参数,将这些参数全部传给func_name的形参里
        start_time = time.time()
        res = func_name(*args,**kwargs)  # 将函数index的返回值赋值给res
        end_time = time.time()
        print(end_time - start_time)
        return res  # 返回被调用index函数的值
    return get_time
def index():
    time.sleep(1)
    print('hello meinv')
index = outer(index)
# index()

这就是完整功能的装饰器,这也就是装饰器的概念,我们在没有改变函数的调用方式的情况下就给函数增加了新的功能

装饰器模板:

装饰器函数的本质是闭包函数

def outer(func_name):  # func_name用于接收被装饰的对象(函数)
    def inner(*args, **kwargs):
        print('执行被装饰函数之前 可以做的额外操作')
        res = func_name(*args, **kwargs)  # 执行真正的被装饰函数
        print('执行被装饰函数之后 可以做的额外操作')
        return res  # 返回真正函数的返回值
    return inner

装饰器语法糖

语法糖:@来降低语句的输出。语法糖在书写的时候应该与被装饰对象紧紧挨着,两者之间不要有空格。

def outer(func_name):
    def inner(*args,**kwargs):
        print('执行被装饰函数之前 可以做的额外操作')
        res = func_name(*args,**kwargs)
        print('执行被装饰函数之后 可以做的额外操作')
        return res
    return inner
@outer  # 语法糖
# 相当于 index = outer(index)
def index():
    time.sleep(1)
    print('hello meinv')
index()
# 语法糖内部原理
    1.使用的时候最好紧跟在被装饰对象的上方
    2.语法糖会自动将下面紧挨着的函数名传给@后面的函数调用

装饰器修复技术

from functools import wraps  
def outer(func_name):
    @wraps(func_name)
    def inner(*args,**kwargs):
        print('执行前')
        res = func_name(*args,**kwargs)
        print('执行后')
        return res
    return inner
@outer
def index():
    time.sleep(1)
    print('hello meinv')
index()

from functools import wraps # 写在函数体代码的开头
	@wraps(func_name)  # 写在函数体代码的第一行
# 用处就是以假乱真,使装饰器过后的函数指向的内存地址也是原来的全局名称空间的函数指向的地址

img

posted @ 2022-03-18 21:03  香菜根  阅读(31)  评论(0编辑  收藏  举报