代码改变世界

day11 -- 函数进阶之闭包、装饰器

2019-07-11 22:31  在上海的日子里  阅读(213)  评论(0编辑  收藏  举报

目  录

一、闭包函数

二、装饰器简介

三、装饰器的推导全过程

四、多层装饰器的装饰和执行过程

五、装饰器修复技术

 

 

一、闭包函数

闭包函数就是指被嵌套定义的函数被外层函数包裹起来,它只能调用外部函数作用域的名字。

二、装饰器简介

装饰器定义:就是一个工具,给被装饰对象添加新的功能。

开放封闭原则:对扩展功能开放,对修改封闭。

装饰器(可调用对象)必须要遵守的两个原则:

  1、不能改变装饰对象的源代码;

  2、不能改变函数调用的方式。

三、装饰器的推导过程

1、装饰器练习(1)----时间戳完整推导

"""
  装饰器的引出,首先要从闭包函数开始:闭包函数其实就是函数的嵌套定义,它必须是包含在一个函数的内部,而且只能调用外部函数作用域的名字。闭包函数的应用场景就是为函数传参的第二种方式,第一种是实参穿给函数的形参,第二种就是闭包。在函数外在包裹一层函数,从外部为函数传参。
  装饰器就是闭包函数的应用。我们想要在不改变源代码和函数原有调用方式的基础上为函数添加新功能,就只能在另一个函数内调用被装饰的函数,在原函数的功能前后添加新的功能。这时就遇到了为原函数传参的情况,怎样解决为内部函数传参?自然就是闭包的原理原函数通过调用外部函数作用域名字来达到目的。

"""
import time

def login(name):
    time.sleep(1)
    print('%s is nb'%name)
    return 'login'
# res = login('egon')
# print(res)

def get_time():
    name = 'egon'
    start = time.time()  # 时间戳
    res = login(name)  # 这边漂黄了,说明逻辑出现问题,检查后发现原函数需要传入参 
                                # 数。所以定义到外部函数的作用域中
    end = time.time()
    print('运行的时间是%s'%(end-start))
    return res
res1 = get_time()
print(res1)
第一步添加功能
"""
上一步我们为login函数添加了时间记录功能,没有改变原函数的代码,但是改变了函数的调用方式,
下一步我们将对其进行优化。

"""
import time

def login(name):
    time.sleep(1)
    print('%s is nb'%name)
    return 'login'
# res = login('egon')
# print(res)

def get_time(name):
    # name = 'egon'  # 将为被装饰函数传参的步骤,提取到外部函数的的形参中,使其不会 
                              # 被定义死。我们可以在调用外部函数的时候为被装饰函数传参
    start = time.time()  # 时间戳
    res = login(name)  # 这边漂黄了,说明逻辑出现问题,检查后发现原函数需要传入参 
                                #  数。所以定义到外部函数的作用域中
    end = time.time()
    print('运行的时间是%s'%(end-start))
    return res
res1 = get_time('jason')  # 将被装饰函数的参数从在外部函数的作用域内定义死升级到外 
                                     # 部函数的形参,在外部函数调用时传入参数
print(res1)
定义死的参数升级为形参
"""
上一步骤是将定义在外部函数作用域内为原函数传入的参数,升级到外部函数的形参位置。可以直接在调用外部函数时为原函数传参
但是,还是没有还原被装饰函数的调用方式。
下一步我们思考如何不改变原函数的调用方式,直接为原函数添加功能
    思路如下:函数名可以作为函数的实参进行传递
"""
import time

def login(name):
    time.sleep(1)
    print('%s is nb'%name)
    return 'login'
# res = login('egon')
# print(res)
def outer(func):
    def get_time(name):
        # name = 'egon'  # 将为被装饰函数传参的步骤,提取到外部函数的的形参中,使 
                                  #  其不会被定义死。我们可以在调用外部函数的时候为被装饰 
                                   #  函数传参
        start = time.time()  # 时间戳
        res = func(name)  # 这边漂黄了,说明逻辑出现问题,检查后发现原函数需要传 
                                   # 入参数。所以定义到外部函数的作用域中
        end = time.time()
        print('运行的时间是%s'%(end-start))
        return res
    return get_time  # 要想运行原函数login,就必须调用get_time函数,调用 
                             #  get_time函数就必须要得到它的函数名,所以执行这一步

res2 = outer(login)  # 执行outer得到get_time函数名,并将其赋值给res2,
# res2('tank')  # 这里的‘tank’就是相当于为get_name传参也就是为原函数login传参,
nn = res2('tank')    # res2('tank) 这一步是调用执行get_time函数,但是它有返回值我 
                             # 们必须要一个参数来接收返回值 res
print(nn)
View Code
"""
上一步我们成功做到了将被装饰函数作为参数进行直接调用,并为其添加新功能。
下一步就是还原函数调用的最后一步:
    思路如下:res2 = outer(login)中的res2可以是任意的一个变量,所以我们可以用一个和原函数一样的函数名进行替代,变身
"""
import time

def login(name):
    time.sleep(1)
    print('%s is nb'%name)
    return 'login'
# res = login('egon')
# print(res)
def outer(func):
    def get_time(name):
        # name = 'egon'  # 将为被装饰函数传参的步骤,提取到外部函数的的形参中,使 
                                      其不会被定义死。我们可以在调用外部函数的时候为被装饰 
                                      函数传参
        start = time.time()  # 时间戳
        res = func(name)  # 这边漂黄了,说明逻辑出现问题,检查后发现原函数需要传 
                                      入参数。所以定义到外部函数的作用域中
        end = time.time()
        print('运行的时间是%s'%(end-start))
        return res
    return get_time  # 要想运行原函数login,就必须调用get_time函数,调用 
                                get_time函数就必须要得到它的函数名,所以执行这一步

login = outer(login)  # 执行outer得到get_time函数名,并将其赋值给login,完成变身
                              # 此时执行login()也就是执行get_time函数,我们通锅get_time 
                                  来给原函数login传递参数!!!
# login('tank')  # 这里的‘tank’就是相当于为get_name传参也就是为原函数login参,
nn = login('tank')    # res2('tank) 这一步是调用执行get_time函数,但是它有返回值我 
                                们必须要一个参数来接收返回值 res
print(nn)
View Code

装饰器语法糖

""""
我们发现要想执行装饰函数的功能,必须要执行最后的三个步骤:
login = outer(login)  # 与原函数名字一样的变量名 = 装饰器函数(被装饰函数)
nn = login('tank')  # 接收返回值的变量 = login(为原函数传递的参数)
print(nn)  # 打印结果
通过以上可以发现步骤很繁琐,所以我们引用“装饰器语法糖”的概念:@outer (注意语法糖一定要紧挨被装饰的函数)
"""
import time

def outer(func):
    def get_time(name):
        # name = 'egon'  # 
        start = time.time()  # 时间戳
        res = func(name)  
        end = time.time()
        print('运行的时间是%s'%(end-start))
        return res
    return get_time  

@outer  # 注意:语法糖的函数体也要写在被装饰函数的上面,先定义再调用
def login(name):
    time.sleep(1)
    print('%s is nb'%name)
    return 'login'
res = login('egon')
print(res)
时间戳完整版

2、装饰器练习(2)----登录认证装饰推导

"""
认证装饰器
    执行函数index之前必须先输入用户名和密码 正确之后才能执行index
    否则提示用户输入错误 结束程序

import time
# 原函数
def login(name):
    time.time()
    print("%s is a open boy"%name)
    return 6969
    
"""
import time

def outer(func):
    def get_login(name):  # 注意不要将传给原函数的形参名和输入的用户名一样,否则 
                                     # 会搞混,原函数就近搜寻变量会误将其作为参数传入
        user_name = input('your name >>>:').strip()
        user_pwd = input('your password >>>:').strip()
        if user_name == 'jason' and user_pwd == '123':
            res = func(name)
            return res
        else:
            print("name or psaaword is error!")
    return get_login
# res2 = outer(login)  # 执行outer函数得到get_函数名,并将其赋值给res2
# nn = res2('tank')  # 调用res2就是调用get_login函数并用变量名nn来接收返回值res
# print(nn)


@outer  # login = outer(login)=get_login
def login(name):
    time.time()
    print("%s is a open boy"%name)
    return 6969
res = login('sean')
print(res)
登录认证完整推导

3、装饰器练习(3)----多层装饰器推导

import time
'''
需求1:添加时间戳功能
'''
def outer1(func):
    def inner1(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        end = time.time()
        print("该程序运行时间是:%s"%(end-start))
        return res
    return inner1

'''
需求2:添加登录认证
'''
def outer2(func):
    def inner2(*args,**kwargs):
        name = input('your name >>>:').strip()
        pwd = input('your password >>>:').strip()
        if name == 'jason' and pwd == '123':
            res = func(*args,**kwargs)
            return res
        else:
            print('name or password is error!')
    return inner2

@outer2  # index = outer2(inner1) = inner2
@outer1 # outer1(index) = inner1
def index():  # 原函数如下:
    time.sleep(1)
    print('index')
    return 'I am from index'
res2 = index()  # 调用inner函数并定义变量名res2来接收原函数的返回值res
print(res2)
两个需求功能

4、装饰器练习(4)----有参装饰器推导

'''
有参装饰器:顾名思义就是需要用到参数的装饰器,但是这个参数与被装饰函数无关。
由下面装饰器模板可知:
def outer(func):
    def inner(*args,**kwargs):
        pass  # 在被装饰函数之前需要添加的功能操作
        res = func(*args,**kwargs)
        pass  # 在被装饰器函数之后添加的功能操作
        return res
    return inner
最外层outer函数的参数是被装饰函数,inner()中的参数是为被装饰函数的传递的参数

思路如下:
    第一种参数传递的方法没办法做到,
    只有第二种参数传递的方法了:闭包函数的方法----我们在装饰器的外面在定义一层函数,来为装饰器传递参数。
示例如下:

'''
import time
# 我们将练习3中的例题加强需求
'''
需求加强如下:
    在登录认证的时候要判断用户信息是在文件里面的还是在MySQL里面的
'''
d = {'is_dict':None}
def outer3(data_sour):  # 注意:可以为装饰器传任意多个参数
    def outer2(func):
        def inner2(*args,**kwargs):
            if d['is_dict']:
                res = func(*args,**kwargs)
                return res
            else:
                if data_sour == 'file':
                    name = input('your name >>>:').strip()
                    pwd = input('your password >>>:').strip()
                    if name == 'egon' and pwd == '123':
                        d['is_dict'] = True
                        res = func(*args,**kwargs)
                        print('认证成功~')
                        return res
                    else:
                        print('name or password is error!')
                elif data_sour == 'MySQL':
                    print('来自数据库的用户。。。')
                elif data_sour == 'Ldap':
                    print('来自 Ldap 的用户!')
                else:
                    print('该用户暂无来源信息。。。')
        return inner2
    return outer2

def outer1(func):
    def inner1(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        end = time.time()
        print('该程序的运行时间是:%s'%(end-start))
        return res
    return inner1

@outer3('file')  # 1,2只是与上面参数的个数相对应,没有实际作用,只是为了说明可以 
                       # 传任意多个参数
#@outer1
def source():  # 原函数
    time.sleep(2)
    print('CQUPT')
    return 'I come from CQUPT!'
res = source()
print(res)
有参装饰器

四、多层装饰器的装饰和执行顺序

def outter1(func1):
    print('加载了outter1')
    def wrapper1(*args,**kwargs):
        print('执行了wrapper1')
        res1=func1(*args,**kwargs)
        return res1
    return wrapper1
def outter2(func2):
    print('加载了outter2')
    def wrapper2(*args,**kwargs):
        print('执行了wrapper2')
        res2=func2(*args,**kwargs)
        return res2
    return wrapper2
def outter3(func3):
    print('加载了outter3')
    def wrapper3(*args,**kwargs):
        print('执行了wrapper3')
        res3=func3(*args,**kwargs)
        return res3
    return wrapper3
@outter1  # index = outter1(wapper2)
@outter2  # wrapper2 = outter2(wrapper3)
@outter3  # wrapper3 = outter3(最原始的index函数内存地址)
def index():
    print('from index')

"""
加载了outter3
加载了outter2
加载了outter1

执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
"""
index()
例题

总结:装饰顺序是从下往上,执行顺序由上向下。

逻辑图如下:

 

五、装饰器修复技术

  装饰器除了要遵循开放封闭原则,在不改变源码和函数调用方式的基础上还要修复掉被修改的痕迹。

"""
用户查看被装饰函数的函数名的时候查看到的就是被装饰函数本身
用户查看被装饰函数的注释的时候查看到的就是被装饰函数的注释
"""

需要用到的功能模块:

             from functools import wraps

所以完整的装饰器模板如下:

from functools import wraps
def outter(func):
    @wraps(func)  # 装饰器修复技术
    def inner(*args,**kwargs):
        """
        我是inner函数
        :param args:
        :param kwargs:
        :return:
        """
        print('执行被装饰函数之前 你可以执行的操作')
        res = func(*args,**kwargs)
        print('执行被装饰函数之后 你可以执行的操作')
        return res
    return inner