python高阶函数闭包装饰器_第十七篇 Python函数之闭包与装饰器

本文深入探讨Python装饰器的原理,包括高阶函数、函数嵌套和闭包。通过实例解析装饰器如何不修改原函数代码和调用方式,同时为函数添加新功能。介绍了如何实现装饰器统计函数运行时间、处理参数、返回值以及模拟认证功能,并展示了如何使用装饰器处理session。
摘要由CSDN通过智能技术生成

一. 装饰器

装饰器:可以拆解来看,器本质就是函数,装饰就是修饰的意思,所以装饰器的功能就是为其他函数添加附加功能。

装饰器的两个原则:

1. 不修改被修饰函数的源代码

2. 不修改被修饰函数的调用方式

实现装饰器的知识储备: 装饰器 = 高阶函数 + 函数嵌套 + 闭包

高阶函数

高阶函数的定义:

1. 函数接收的参数是一个函数名

2. 函数的返回值是一个函数名

3.满足上述条件任意一个,都可以称之为高阶函数

importtime#示例1: 函数接收的参数是一个函数名#def foo():#print("Hello")#

#def test(func):#print(func)#func()#

#test(foo)

#示例2:函数test接收的参数是一个函数名foo,可以直接为foo函数增加一个功能#需求:想统计foo运行的时间,结果这个高阶函数修改了函数的源代码

#def foo():#time.sleep(2)#print("Hello")#

#def test(func):#print(func)#start_time = time.time()#func()#stop_time = time.time()#print('函数的运行时间是:%s '%(stop_time-start_time)) # 统计的是func()的运行时间,即foo的运行时间#

#test(foo)

#示例3:函数的返回值是一个函数名

deffoo():print("from the foo")deftest(func):returnfunc#实现了装饰器的不改变函数的调用方式这个功能

foo =test(foo)

foo()#示例4: 示例2+示例3就能实现一个半完整的装饰器

deffoo():

time.sleep(3)print('来自foo')#不修改foo源代码#不修改foo调用方式

# 没有修改被修饰函数的源代码,也没有修改被修饰函数的调用方式,但是也没有为被修饰函数添加新功能

deftimer(func):

start_time=time.time()

func()

stop_time=time.time()print('函数的运行时间是:%s'%(stop_time-start_time)) #统计的是func()的运行时间,即foo的运行时间

returnfunc

foo= timer(foo) #timer函数的返回值是foo,在把返回值又赋值给了foo,得到一个内存地址

foo() #有了内存地址,直接加个()就可以运行。#结果#来自foo#函数的运行时间是:3.001082420349121#来自foo # 上面说的是半完整的装饰器,是因为这里多运行了一次foo,肯定不行那,这就导致这个装饰器就不合格了

函数嵌套

#函数嵌套及作用域

# father()函数里嵌套了一层son()函数,son()函数里又嵌套了一层grandson()函数

deffather(name):print('from father %s' %name) #name变量的作用域,是传进来的DDD

def son(): #函数也是变量

name='abc'

print('我的爸爸是%s' %name) #作用域是son函数自己定义的name变量

defgrandson():print('我的爷爷是%s' % name) #grandson函数没有自己的name变量,需要往上一级一级找,找到了son函数,就是abc

#同调用son的道理一样,需要通过father调用son,通过son函数再调用grandson函数

grandson()#如果要调用son函数,不能跑到father的外面去调用,会报错;只能通过调用father来调用son函数

#此处是调用son函数

son()

father('DDD')

son('abcx') # 报错 # son函数定义在father里,所以只能通过father调用son函数

闭包

上面理解了函数的层级嵌套,就很好理解闭包的概念。嵌套的函数换个名称叫闭包

包:就像一个盒子套着一个盒子,一层层往外套,每一次都是一个包

闭:就是封装,封装变量。每个包都有被封装的变量。

下面画图解释

4d88eec40c8117e07d8304a824b65691.png

闭包如何找变量?

还是先从自己的闭包里开始找变量,自己没有在往上一层包里找变量,如果还没有,可以一直往外找,可以一直找到最外层

装饰器基本实现

首先,#搭个装饰器的架子,满足高阶函数,满足函数嵌套

importtimedeftimer(func):defwrapper():#print(func) # 嵌套函数也可以获取传入的参数,传入的参数也是个函数,作用域在起作用 这是个内存地址

func() #有了内存地址,加()就可以执行func函数

returnwrapperdeftest():

time.sleep(2)print("test函数运行完毕")

其次,

有个需求:不修改test函数的原代码,也不修改test函数的调用方式,而且为test函数增加一个统计运行时间的功能

实现该需求的流程分析及代码运行过程:#流程分析:#1. 把test函数传给timer,直接返回的是wrapper的地址,而没有运行wrapper函数

res =timer(test)#2. 上面返回的是wrapper函数的内存地址,所以就可以直接加()执行wrapper函数

res() #执行的是wrapper函数#3. 再看wrapper函数做了什么?#wrapper 函数运行的是func函数,而func函数就是传入的test()函数,所以执行wrapper就可以执行test()函数#所以,这个时候就可以添加代码来统计test函数的运行时间了。

至此,#上面的代码就演化成为:

deftimer(func):defwrapper():

start_time=time.time()

func()

stop_time=time.time()print("test函数的运行时长是: %s" % (stop_time -start_time))returnwrapperdeftest():

time.sleep(2)print("test函数运行完毕")

res=timer(test)

res()#运行结果

test函数运行完毕

test函数的运行时长是:2.000473976135254但是,这里有 个问题,需求是不修改test函数的调研方式,test函数的调用方式应该是test(),而这里的调用方式是res()

所以,解决修改了test函数调用方式这个问题的方法:#将timer(test)的值赋给变量test

test =timer(test)#然后再test()调用,这样就达到了没有修改test函数调用方式的目的

test()#到这里,一个装饰器的架子和调用模型就初步成型了,但是还需要赋值给test变量这一步操作

#所以,最后,

python为了实现这个不修改test函数调用方式,也省略赋值这行代码,而产生了一个装饰器的概念#上面的代码就进化为:#装饰器

deftimer(func):defwrapper():

start_time=time.time()

func()

stop_time=time.time()print("test函数的运行时长是: %s" % (stop_time -start_time))returnwrapper

@timer#在test函数的顶头加上 @timmer来修饰test函数, 就相当于 test=timmer(test) 这个赋值动作

deftest():

time.sleep(2)print("test函数运行完毕")#有了装饰器,就可以直接调用test函数

test()#终于啊,老天爷啊,历经千辛万苦,终于完美的满足了最开始的需求

为闭包加上返回值

还是接上面的基本装饰器来说

deftimer(func):defwrapper():

start_time=time.time()

func()

stop_time=time.time()print("test函数的运行时长是: %s" % (stop_time -start_time))returnwrapper#需求:给test函数加一个返回值: “test函数返回好消息了”

@timerdeftest():

time.sleep(2)print("test函数运行完毕")return "test函数返回好消息了"res=test()print(res)#结果

test函数运行完毕

test函数的运行时长是:2.000929117202759None 返回None了#为什么会这样呢?分析如下:#1. 运行test函数,因为有装饰器,实际上运行的是wrapper这个闭包,而wrapper函数却没有返回值,所以返回的是None,那么给wrapper函数加个返回值呢?

代码如下:deftimer(func):defwrapper():

start_time=time.time()

func()

stop_time=time.time()print("test函数的运行时长是: %s" % (stop_time -start_time))return 123 #给wrapper函数加的返回值

returnwrapper

@timerdeftest():

time.sleep(2)print("test函数运行完毕")return "test函数返回好消息了"res=test()print(res)#结果

test函数运行完毕

test函数的运行时长是:2.0010011196136475

123 #返回的是123,

#上面运行返回的是123,而我们的需求是要返回“test函数返回好消息了”#接着开,wrapper函数运行的是func函数,而func函数就是传入的test函数,所以把 func()赋值给变量 res呢?

#代码如下:

deftimer(func):defwrapper():

start_time=time.time()

res= func() #赋值给res

stop_time =time.time()print("test函数的运行时长是: %s" % (stop_time -start_time))return res #返回res

returnwrapper

@timerdeftest():

time.sleep(2)print("test函数运行完毕")return "test函数返回好消息了"res=test()print(res)#结果

test函数运行完毕

test函数的运行时长是:2.0009818077087402test函数返回好消息了#至此,给装饰器加上返回值就实现了。

#但是还有个问题,如果直接调用test呢?

test()#结果如下,为什么?暂时还不明白

test函数运行完毕

test函数的运行时长是:2.0001914501190186

为闭包加上参数

ad641f0d0a80d3112b2662e4d54896db.png

#需求:给test传入参数

importtimedef timmer(func): #func=test1

defwrapper():

start_time=time.time()

res=func()

stop_time=time.time()print('运行时间是%s' %(stop_time-start_time))returnresreturnwrapper

@timmer#test=timmer(test)

deftest(name,age):

time.sleep(3)print('test函数运行完毕,名字是【%s】 年龄是【%s】' %(name,age))return '这是test的返回值'res=test('lixiao',age=18) #就是在运行wrapper

print(res)#结果:

报错

为什么呢?

因为运行test就是在运行wrapper,而test传入了参数,wrapper里却没有接受参数,所以就报错了。那怎么改呢?1. 礼尚往来,你给我得接着呀,所有wrapper需要有参数接受test传来的值。2. wrapper 接受到了传来的值,wrapper函数体内,运行的res=func()实际上就是test(),所以,wrapper接受的参数也要传给func()才行。importtimedef timmer(func): #func=test1

def wrapper(*args,**kwargs):

start_time=time.time()

res=func(*args,**kwargs)

stop_time=time.time()print('运行时间是%s' %(stop_time-start_time))returnresreturnwrapper

@timmer#test=timmer(test)

deftest(name,age):

time.sleep(3)print('test函数运行完毕,名字是【%s】 年龄是【%s】' %(name,age))return '这是test的返回值'

#备注:

wrapper(*args,**kwargs):表示的是可变长参数#*args: 代表把所有的位置参数接成列表#**kwargs: 代表可能用户传值的时候通过关键字传值,比如用户传的值是age =18, 或者{"age": 18}形式的, 就用**kwargs来接

#所以,*args,**kwargs :代表可变长参数,传多少参数,任意形式的都可以接受了

@timmerdeftest1(name,age,gender):

time.sleep(1)print('test1函数运行完毕,名字是【%s】 年龄是【%s】 性别【%s】' %(name,age,gender))return '这是test的返回值'test1('alex', 18, gender='male')

闭包:补充解压序列 (很有用)

#需求:有个很长的列表,拿出第一个和最后一个值

l= [1,2,4,5,6,123,34,1,6]

方式1:索引的方式print(l[0])print(l[-1])

方式2:解压序列方式

l= [1,2,4,5,6,123,34,1,6]

a,*_, c =lprint(a) #1

print(*_) #2 4 5 6 123 34 1

print(c) #6

#a 代表取第一个值#* 代表所有的值,下划线_表示不要了,当然下划线可以有任意字符替换, 如 *b, *d都可以#c 代表最后一个值了

#需求:a =1, b=2, 交换a,b的值

a = 1b= 2a,b=b,aprint(a) #2

print(b) #1

函数闭包为函数加上认证功能

defauth_func(func):def wrapper(*args, **kwargs):

username= input('用户名:').strip()

password= input("密码:").strip()if username =="sb" and password == "123321": # 用户是写死了

res= func(*args, **kwargs)return res #切记要有返回值那

else:print("用户名或密码错误")returnwrapper

@auth_funcdefindex():print("欢迎来到京东")

@auth_funcdefhome():print("欢迎回家")

@auth_funcdefshopping_box(name):print("%s的购物车里有%s,%s,%s" % (name, '娃娃', '电脑','手机'))

index()

home()

shopping_box('韩梅梅')

函数闭包模拟session

上面为每个功能都加了验证功能,但是每次运行其他功能都需要重新登陆,这显然是不合理的,可以用session和cookie来解决,那怎么模拟解决这个问题呢?

还有一个问题,就是用户是写死的,而实战中,我们的用户不可能只有一个,而且写死,所以这里也是需要改造的。

#用户一般都是存在数据库里,这里为了方便模拟,就把用户存储在一个列表里

user_list =[

{'name': 'sb', 'passwd': '123'},

{'name': 'lhf', 'passwd': '123'},

{'name': 'wpq', 'passwd': '123'},

{'name': 'yh', 'passwd': '123'},

]

current_dic= {'username':None, 'login':False} # 模式当前登录状态是FALSE,未登录defauth_func(func):def wrapper(*args, **kwargs):#在运行wrapper之前, 先判断是否有用户登录

if current_dic['username'] and current_dic['login']: #这个代表的是由用户登录且是true,然后直接运行func()

res= func(*args, **kwargs)returnres#如果上面的判断不为True,则输入用户名和密码

username = input("用户名:")

password= input("密 码:")#输完后应该从user_list里提取信息

for user inuser_list:#然后判断输入的用户名和密码与user_list里的是否一致

if username == user['name'] and password == user['passwd']:#如果有一致的, 要把当前状态改一下

current_dic['username'] = username #记录状态的字典里用户名 = 用户输入的用户名

current_dic['login'] = True #登陆状态改为true

res = func(*args, **kwargs)return res #切记要有返回值那

#如果遍历了一遍,都没有匹配的

else:print("用户名或密码错误")returnwrapper

@auth_funcdefindex():print("欢迎来到京东")

@auth_funcdefhome():print("欢迎回家")

@auth_funcdefshopping_box(name):print("%s的购物车里有%s,%s,%s" % (name, '娃娃', '电脑','手机'))

index()

home()

shopping_box('韩梅梅')

函数闭包带参数装饰器-----带参数的装饰器

user_list=[

{'name':'alex','passwd':'123'},

{'name':'linhaifeng','passwd':'123'},

{'name':'wupeiqi','passwd':'123'},

{'name':'yuanhao','passwd':'123'},

]

current_dic={'username':None,'login':False}def auth(auth_type='filedb'):defauth_func(func):def wrapper(*args,**kwargs):print('认证类型是',auth_type)if auth_type == 'filedb':if current_dic['username'] and current_dic['login']:

res= func(*args, **kwargs)returnres

username=input('用户名:').strip()

passwd=input('密码:').strip()for user_dic inuser_list:if username == user_dic['name'] and passwd == user_dic['passwd']:

current_dic['username']=username

current_dic['login']=True

res= func(*args, **kwargs)returnreselse:print('用户名或者密码错误')elif auth_type == 'ldap':print('鬼才特么会玩')

res= func(*args, **kwargs)returnreselse:print('鬼才知道你用的什么认证方式')

res= func(*args, **kwargs)returnresreturnwrapperreturnauth_func

@auth(auth_type='filedb') #auth_func=auth(auth_type='filedb')-->@auth_func 附加了一个auth_type --->index=auth_func(index)

defindex():print('欢迎来到京东主页')

@auth(auth_type='ldap')defhome(name):print('欢迎回家%s' %name)#@auth(auth_type='sssssss')defshopping_car(name):print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','妹妹','娃娃'))#print('before-->',current_dic)

index()#print('after--->',current_dic)

home('产品经理')

shopping_car('产品经理')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值