首页 > 技术文章 > 10. 函数闭包与装饰器

superpoint 2021-07-20 00:28 原文

闭包函数

闭包函数=名称空间与作用域+函数嵌套+函数对象(综合应用)

闭包函数:保障数据安全

# 只存在嵌套函数中
# 内层函数使用外层非全局变量的引用
def func():
	a=[]
    def func1():
        a.append(1)
    return func1

这样a变量与函数func1绑定,在内存中不会消失

像a这种变量叫做自由变量

装饰器

定义:在不改变原函数的代码以及调用方式的前提下,为被装饰对象增添新的功能

开放封闭原则:开放是指对代码的拓展是开放的,封闭是指对源码的修改是封闭的

推导方式和语法糖:

# 原函数
def index(x,y)
	return x+y
import time
# 现在想要添加一个功能,能够测量函数执行的时间

# 推导过程一:
def wrapper():
    start=time.time()
    index(111,222)
    stop=time.time()
    print(stop - start)
wrapper()
# 这样虽然功能实现了,但是传参数的时候有问题,如果传递的参数太多无法执行

# 二:解决参数问题
import time
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))
def wrapper(*args,**kwargs):
    start=time.time()
    index(*args,**kwargs) # index(3333,z=5555,y=44444)
    stop=time.time()
    print(stop - start)
wrapper(3333,4444,5555)
wrapper(3333,z=5555,y=44444)

# 三:因为装饰器应该可以装饰不同的函数,最好的解决办法就是将被装饰的函数当做参数传进去
import time
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))
def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)
def outter(func):
    # func = index的内存地址
    def wrapper(*args,**kwargs):
        start=time.time()
        func(*args,**kwargs) # index的内存地址()
        stop=time.time()
        print(stop - start)
index=outter(index) # index=wrapper的内存地址
home=outter(home) # home=wrapper的内存地址
home('egon')  # 虽然这个home并不是函数home本身(两个home的id并不同),但是对于用户来说是一样的

# 四:解决原函数的返回值问题
import time
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))
def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)
def outter(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print(stop - start)
        return res
    return wrapper  # 原函数返回给res,res返回给wrapper,wrapper再返回给outter
# 偷梁换柱:home这个名字指向的wrapper函数的内存地址
home=outter(home)
res=home('libai') # 这样就和原来的调用方式一样了


##### 语法糖:方便装饰器的使用 #####
import time
def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print(stop - start)
        return res
    return wrapper
# 在被装饰对象正上方的单独一行写@装饰器名字
@timmer # 就相当于执行了index=timmer(index),为什么叫语法糖,就是单纯觉得长得像而已
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))
@timmer # home=timmer(ome)
def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)
index(x=1,y=2,z=3)
home('egon')

##### 总结无参装饰器模板 #####
def outter(func):
    def wrapper(*args,**kwargs):
        # 在函数执行之前为其添加的新的操作
        res=func(*args,**kwargs)
        # 在函数执行之后为其添加的新的操作 
        return res
    return wrapper

# 装饰器就是将原函数名指向的内存地址偷梁换柱成wrapper函数,所以应该将wrapper做的跟原函数一样才行,这里的一样应该是除了函数名的内存地址,其他的包括功能、传参、以及内置的属性都应该一样的才对,上面解决了传参和功能,下面说一下属性的问题(就是内置的__name__、__doc__等方法)
from functools import wraps
def outter(func):
    @wraps(func)
	# 该模块的装饰器wraps能够将函数的内置属性传给装饰器
	# 1. 函数wrapper.__name__ = 原函数.__name__
	# 2. 函数wrapper.__doc__ = 原函数.__doc__
	# wrapper.__name__ = func.__name__
	# wrapper.__doc__ = func.__doc__
    def wrapper(*args, **kwargs):
        """这里面是主要功能"""
        res = func(*args, **kwargs) # res=index(1,2)
        return res
    return wrapper

##### 下面解决一下装饰器传参的方法,装饰器有的时候可能根据传参的不同,实现不同的功能选择,如根据数据来源的不同选择不同的验证方式
# 推导过程介绍:装饰器的功能是在wrapper函数加上的,因为最后执行的是wrapper函数
def auth(func,db_type):
    def wrapper(*args, **kwargs):
        name=input('your name>>>: ').strip()
        pwd=input('your password>>>: ').strip()
        if db_type == 'file':
            print('基于文件的验证')
            if name == 'egon' and pwd == '123':
                res = func(*args, **kwargs)
                return res
            else:
                print('user or password error')
        elif db_type == 'mysql':
            print('基于mysql的验证')
        elif db_type == 'ldap':
            print('基于ldap的验证')
        else:
            print('不支持该db_type')
    return wrapper
@auth  # 账号密码的来源是文件
def index(x,y):
    print('index->>%s:%s' %(x,y))
@auth # 账号密码的来源是数据库
def home(name):
    print('home->>%s' %name)
@auth # 账号密码的来源是ldap
def transfer():
    print('transfer')
# 要实现上述的功能,就要多传进去一个参数,下面的方法是可以实现的   
index=auth(index,'file')
home=auth(home,'mysql')
transfer=auth(transfer,'ldap')
# 但是这样是无法使用语法糖的,因为语法糖@deco只实现了index=auth(index),没有传进去参数
# 推导二:想法就是多写一层,通过返回值传递的方式将auth(index,'file')的值传给deco(index)
def auth(db_type):
    def deco(func):
        def wrapper(*args, **kwargs):		
            name=input('your name>>>: ').strip()
            pwd=input('your password>>>: ').strip()      
            if db_type == 'file':
                print('基于文件的验证')
                if name == 'egon' and pwd == '123':
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('user or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该db_type')
        return wrapper
    return deco
deco=auth(db_type='file')
@deco # 账号密码的来源是文件
def index(x,y):
    print('index->>%s:%s' %(x,y))
deco=auth(db_type='mysql')
@deco # 账号密码的来源是数据库
def home(name):
    print('home->>%s' %name)
deco=auth(db_type='ldap')
@deco # 账号密码的来源是ldap
def transfer():
    print('transfer')
index(1,2)
home('egon')
transfer()
# 这里,装饰器是没问题了,但是装饰器的使用出现问题了,我们想要@deco,但是函数实际上是auth,所以这里就是将auth(db_type='ldap')的值传给deco,进而实现deco的功能
# 正式版本:
def auth(db_type):
    def deco(func):
        def wrapper(*args, **kwargs):
            name = input('your name>>>: ').strip()
            pwd = input('your password>>>: ').strip()

            if db_type == 'file':
                print('基于文件的验证')
                if name == 'egon' and pwd == '123':
                    res = func(*args, **kwargs)  # index(1,2)
                    return res
                else:
                    print('user or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该db_type')
        return wrapper
    return deco

@auth(db_type='file')  # 实际上就是@deco,只是将(db_type='file')的值传给了deco,从而实现语法糖传参
def index(x, y):
    print('index->>%s:%s' % (x, y))

@auth(db_type='mysql')  # @deco # home=deco(home) # home=wrapper
def home(name):
    print('home->>%s' % name)

@auth(db_type='ldap')  # 账号密码的来源是ldap
def transfer():
    print('transfer')

index(1, 2)
home('egon')
transfer()

##### 有参装饰器模板:比起无参装饰器就是多套了一层 #####
def 有参装饰器(x,y,z):
    def outter(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
        return wrapper
    return outter


@有参装饰器(1,y=2,z=3)
def 被装饰对象():
    pass

叠加多个装饰器的分析

​ 下面分析一下函数被添加多个装饰器的时候如任何加载和执行

​ 结论就是加载的时候是自下而上的,执行的时候是在上而下的

def deco1(func1): # func1 = wrapper2的内存地址
    def wrapper1(*args,**kwargs):
        print('正在运行===>deco1.wrapper1')
        res1=func1(*args,**kwargs)
        return res1
    return wrapper1

def deco2(func2): # func2 = wrapper3的内存地址
    def wrapper2(*args,**kwargs):
        print('正在运行===>deco2.wrapper2')
        res2=func2(*args,**kwargs)
        return res2
    return wrapper2

def deco3(x):
    def outter3(func3): # func3=被装饰对象index函数的内存地址
        def wrapper3(*args,**kwargs):
            print('正在运行===>deco3.outter3.wrapper3')
            res3=func3(*args,**kwargs)
            return res3
        return wrapper3
    return outter3
##### 加载的顺序(就是代码没执行的时候)是自下而上#####
@deco1      # index=deco1(wrapper2的内存地址)        ===> index=wrapper1的内存地址
@deco2      # index=deco2(wrapper3的内存地址)        ===> index=wrapper2的内存地址
@deco3(111) # ===>@outter3===> index=outter3(index) ===> index=wrapper3的内存地址
def index(x,y):
    print('from index %s:%s' %(x,y))
##### 执行代码的顺序是自上而下的 #####
# 装饰器实际上也是一个函数
# 执行代码的时候,deco1就是装饰的是deco2,指向的是wrapper2的内存地址,deco2装饰的是deco3,指向的是wrapper3的内存地址,deco3(本质上是outter)装饰的是被装饰函数index,就这样一个一个的传递下来,执行所有的代码

推荐阅读