函数篇:闭包函数、装饰器

2022.3.18

qFjgsg.jpg

  • 闭包函数
  • 闭包函数的实际应用
  • 装饰器简介
  • 简易版本装饰器
  • 进阶版本装饰器
  • 完整版本装饰器
  • 装饰器模板
  • 装饰器语法糖
  • 装饰器修复技术

一、闭包函数

1、什么是闭包函数

两大特征:

闭:定义在函数内部的函数

包:内层函数使用了外层函数名称空间中的名字

def outer():
    a = 111
    inner():
        print(a)

如上代码所示,函数是嵌套型,符合定义在函数内部,同时内函数使用了外函数的变量a,因此这就是一个闭包函数,暂不考虑其他条件

2、闭包函数的应用

我们已经知道了闭包函数的两大特征,那么闭包函数引用外部函数的名字有几种形式呢

(1)在外层函数体代码寻找名字

def outer():
    a = 111
    def inner():
        print(a)
    return inner
res = outer()
res()  # 111

(2) 在外层函数括号内找参数

1.默认形参,相当于写死
def outer(a=111):
    def inner():
        print(a)
    return inner
2.外部传参
def outer(a):
	def inner():
        print(a)
    return inner
res = outer(111)
res()  # 111

以上是闭包函数的基本使用,首先要理解闭与包的概念,这其实也是给后面的内容做铺垫。

二、装饰器

1、装饰器简介

概念:装饰器的本质其实是在不改变原有对象的调用方式和内部代码的前提下,加以修饰或添加新的功能

原则:对拓展开放、对修改封闭

这里举个例子,但是首先需要了解一个概念,时间戳

import time

print(time.time()) # 结果是一个时间戳

时间戳:

1970年1月1日0时0分0秒距离刚刚代码运行的间隔秒数

然后我们来看下面这个函数:

import time
def index():
    time.sleep(1)  # 表示后面的代码一秒钟以后执行
    print(666)    

这个函数是表示停留一秒钟打印666出来,那么如果我们需要打印出来这个代码运行的时间,怎么做呢?

import time
def index():
    time.sleep(1)  
    print(666) 
    
def test():
    start_time = time.time()
    index()  # 在函数test内调用index函数
    end_time = time.time()
    print(start_time - end_time)
test()  # 调用test函数

这样看来我们重新定义一个函数,从而解决了这个需求,能够通过test函数计算index函数体代码运行的时间,但是我们发现通过定义其他函数,对index函数进行了功能增加,但是不符合装饰器的特征,因为改变了调用方式

那么如何不改变调用方式的情况下,调用index函数,同时增加功能呢,这里就需要结合之前学过的闭包函数加以改良了。

2、简易版本装饰器

怎么改良呢?结合闭包函数,我们可以将上面test函数封装起来,外面再来一层函数

import time
def index():
    time.sleep(1)  
    print(666) 

def outer():
    def test():
        start_time = time.time()
        index()  # 在函数test内调用index函数
        end_time = time.time()
        print(start_time - end_time)
    return test  #将test函数名作为outer函数的返回值
res = outer()  # 将outer函数的返回值赋值给res
res()  # 加上括号调用res就相当于调用test

现在我们发现这样的话我们还是通过res去调用的test从而调用的index,还是没有解决调用方式的问题,同时内部的函数也不是闭包函数,因为只有闭没有包,这时候就需要向内传递一个参数了。

import time
def index():
    time.sleep(1)  
    print(666) 

def outer(func):
    def test():
        start_time = time.time()
        func()  # 执行传进来参数的函数
        end_time = time.time()
        print(start_time - end_time)
    return test
outer(index)  # 将index作为outer的参数传进去
res = outer(index)  # 赋值给变量res,res接收outer的返回值test,因此res=test
res()  # 调用res,相当于test()

这样的话,里面的函数使用了外部纯进来的参数,形成了一个闭包函数,同时传进来的参数是index函数名,因此又激活了原index函数的调用,此时是用res调用test,看起来绕了一圈还是在用别的函数调用index,怎么解决呢?

其实可以发现res是一个变量名,知识为了接收outer的返回值,那么这个变量可不可以使用index呢,我们都知道变量赋值先看右边在看左边,那么使用index的话,就可以使用index()来调用封装好的test函数了,而test又是为了优化原来的index函数的,这样就解决了调用方式的问题,我们来试试吧!

import time
def index():
    time.sleep(1)  
    print(666) 

def outer(func):
    def test():
        start_time = time.time()
        func()  # 执行传进来参数的函数
        end_time = time.time()
        print(start_time - end_time)
    return test
index = outer(index)  # 相当于index = test
index()  # 相当于test()

这样看来,就实现了使用原来函数名调用原来函数,并不改变原有函数代码并增加功能,是不是有种俄罗斯套娃的感觉呢

3、进阶版本装饰器

通过上面我们发现,index的问题是解决了,那么假如,index里面有参数呢,或者其他函数有参数,多个函数有不同参数,怎么传值呢,这里其实可以引用之前的可变长参数的概念

def outer(func):
    def test(*args,**kwargs):  # 可以接收任何参数也可无参数
        start_time = time.time()
        func(*args,**kwargs)  # 将传进来的参数打散再用
        end_time = time.time()
        print(start_time - end_time)
    return test

这样是不是就得到了一个可以接收任何参数的装饰器了呢,是的!

4、完整版本装饰器

是不是以为这样就结束了呢,其实不是,我们发现如果要修改的函数有返回值的话,我们的装饰器没有返回值,所以就需要将返回值也模拟出来,可以这样做

def outer(func):
    def test(*args,**kwargs):  # 可以接收任何参数也可无参数
        start_time = time.time()
        res = func(*args,**kwargs)  # 将func的返回值赋值给res
        end_time = time.time()
        print(start_time - end_time)
        return res  # 返回res,就是返回原函数的返回值
    return test

这样就如假包换了

其实,上面的过程主要是为了帮助我们理解装饰器的概念和过程,其实我们完全可以套用模板

5、装饰器模板

def outer(func):
    def inner(*args, **kwargs):
        '''被装饰函数前需要增加的代码'''
        res = func(*args, **kwargs)
        '''被装饰函数后需要增加的代码'''
        return res
    return inner

index = outer(index)
index()  # 调用index

6、装饰器语法糖

@+装饰器函数

def outer(func):
    def inner(*args, **kwargs):
        '''被装饰函数前需要增加的代码'''
        res = func(*args, **kwargs)
        '''被装饰函数后需要增加的代码'''
        return res
    return inner

@oueter  # 语法糖,跟在一个函数前,是对这个函数的装饰,这句话等价于:index = outer(index)
def index():
    pass

所以语法糖就是我们之前给原函数做的一次赋值,只不过简化了写法

语法糖:
1.语法糖@函数名,写在被装饰函数的上方
2.语法糖会将下面紧挨着的函数名作为参数传入装饰器

三、装饰器修复技术

本质是为了让装饰器做到跟原函数完全一样,甚至指向的内存地址都一样,写法如下:

from functools import wraps  # 固定搭配,写在这里!!!
def outer(func):
    @wraps(func)  # 固定搭配,写在这里!!!
    def inner(*args, **kwargs):
        '''被装饰函数前需要增加的代码'''
        res = func(*args, **kwargs)
        '''被装饰函数后需要增加的代码'''
        return res
    return inner

@outer
def index():
    pass

qFjHQU.md.jpg

四、作业

练习:编写一个用户认证装饰器

1.基本要求
执行每个函数的时候必须先校验身份 eg: jason 123

2.拔高练习(有点难度)
执行被装饰的函数 只要有一次认证成功 那么后续的校验都通过
register login transfer withdraw

提示:全局变量 记录当前用户是否认证

'''
练习:编写一个用户认证装饰器

1.基本要求
  	执行每个函数的时候必须先校验身份 eg: jason 123

2.拔高练习(有点难度)
  	执行被装饰的函数 只要有一次认证成功 那么后续的校验都通过
    	register login transfer withdraw

提示:全局变量 记录当前用户是否认证
'''


# 定义校验管理员身份的装饰器
a = 0
from functools import wraps
def account(func):
    @wraps(func)
    def check_account(*args, **kwargs):
        global a
        while a == 0:
            username = input('Input username>>>:').strip()
            password = input('Input password>>>:').strip()
            if username == 'jason' and password == '123':
                print('Administrator login!')
                a = 1
            else:
                print('Sorry,error message!')
        res = func(*args, **kwargs)
        return res
    return check_account


b = {}
@account
def register():
    regist_name = input('Input account>>>:')
    if regist_name not in b:
        regist_pwd = input('Input password>>>:')
        b[regist_name] = regist_pwd
        print('Regist success!')
    else:
        print('用户名已存在!')

@account
def login():
    login_name = input('Input account>>>:')
    login_pwd = input('Input password>>>:')
    if login_name in b and login_pwd == b[login_name]:
        print('Login success!')
    else:
        print('Login failed')

@account
def delete():
    del_name = input('Input account>>>:')
    if del_name in b:
        del b[del_name]
        print('删除成功!')
    else:
        print('Account not found!')

@account
def check():
    for i in b:
        print('''
        Account name:{0}
        password:{1}
        '''.format(i, b[i]))

@account
def exit():
    global tag
    tag = False


@account
def empty():
    pass


empty()
while True:
    print('''
        ----------------------
        1.注册
        2.登录
        3.删除
        4.查看
        5.退出
        ----------------------
        ''')
    choice = input('输入功能编号>>>:')
    if choice == '1':
        register()
    elif choice == '2':
        login()
        break
    elif choice == '3':
        delete()
    elif choice == '4':
        check()
    elif choice == '5':
        exit()
    else:
        print('指令错误!!!')

posted @ 2022-03-18 19:17  马氵寿  阅读(103)  评论(0)    收藏  举报