(十)Python装饰器
装饰器:本质就是函数,功能是为其他函数添加附加功能。
两个原则:
1.不修改被修饰函数的源代码
2.不修改被修饰函数的调用方式
一个栗子
- def test():
- res = 0
- for i in range(50):
- res += i
- print(res)
- test()
结果:1225
需求:要想计算上述函数的运行时间,既不修改被修饰函数的源代码,又不修改被修饰函数的调用方式,关键还要能添加进附加功能,如何实现?
如下,初级版本一:直接修改了被修饰函数的源代码
- import time
- def test():
- start_time = time.time()
- res = 0
- for i in range(50):
- time.sleep(0.1)
- res += i
- print(res)
- end_time = time.time()
- print('执行的时间 %s' %(end_time - start_time))
- test()
结果:1225
执行的时间 5.027297258377075
这么做是不可行的,如果有一百个函数呢,需要去改一百个函数的源代码。
Python装饰器的知识要点:
装饰器=高阶函数+函数嵌套+闭包
我们从这三个知识点,循序渐进,一点点演变,最后完成上述的需求
先看使用高阶函数的版本二:
- import time //被修饰函数的源代码没有动
- def test():
- res = 0
- for i in range(50):
- res += i
- print(res)
- def print_time(func):
- start_time = time.time()
- func()
- time.sleep(1)
- end_time = time.time()
- print('执行的时间 %s' %(end_time - start_time))
- print_time(test) //被修饰函数的调用方式被修改了
结果:1225
执行的时间 1.0009217262268066
被修饰函数的源代码确实没有修改,但是被修饰函数的调用方式发生了变化,不合格。
再看使用高阶函数的版本三:
- import time
- def test():
- res = 0
- for i in range(50):
- res += i
- print(res)
- def print_time(func):
- start_time = time.time()
- func()
- time.sleep(1)
- end_time = time.time()
- print('执行的时间 %s' %(end_time - start_time))
- return func //这步是关键,把当参数穿进去的函数,再return回来,为了后面赋值给原本的函数名test,这样就可以不修改被修饰函数的调用方式
- test = print_time(test) //赋值给原本的函数名test
- test() //被修饰函数的调用方式没有变化
结果:1225
执行的时间 1.0000982284545898
1225
看似符合要求了,但是我们看运行结果,test()被执行了两遍,还是不合格。
所以光靠高阶函数,无法达到目标,现在来看函数嵌套和闭包
函数嵌套是指在一个函数里定义另一个函数,而不是在一个函数里调用另一个函数
闭包:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。它只不过是个“内层”的函数,由一个名字(变量)来指
代,而这个名字(变量)对于“外层”包含它的函数而言,是本地变量。
结果为:from grandson nick
这个例子就是函数嵌套+闭包
执行grandson(),先从自己内部找name这个变量,没有的话从son()找,没有的话再从super()找,最后就用了super()传进来的参数,这其实就是函数作用域。
也就是说,从最外层super()传一个参数,里面嵌套的函数都可以接受到这个参数
高阶函数+函数嵌套+闭包的版本四:
- import time
- def test():
- time.sleep(3)
- print('函数执行完毕')
- def timer(func):
- def wrapper():
- start_time = time.time()
- func() //闭包,取的是函数外部的参数,就是在运行test()
- end_time = time.time()
- print('运行时间%s' %(end_time - start_time))
- return wrapper
- test = timer(test) //返回的是wrapper的地址
- test() //执行的是wrapper()
结果:函数执行完毕
运行时间3.0003645420074463
到此,没有改原函数的源代码,没有改变调用方式,并实现了附加功能。
但是在调用函数之前还多了一步赋值:test = timer(test)
所以这里加入Python的语法糖,在需要被修饰的函数前加上@timer装饰器
最终版本为:
- import time
- def timer(func):
- def wrapper():
- start_time = time.time()
- func()
- end_time = time.time()
- print('运行时间%s' %(end_time - start_time))
- return wrapper
- @timer //就相当于test=timer(test)
- def test():
- time.sleep(3)
- print('函数执行完毕')
- test()
结果:函数执行完毕
运行时间3.0003645420074463
这里又出现一个问题,那就是如果被修饰函数有return返回值,执行test()将拿不到返回值,如下
- import time
- def timmer(func):
- def wrapper():
- start_time = time.time()
- func()
- end_time = time.time()
- print('函数执行的时间%s' %(end_time - start_time))
- return wrapper
- @timmer
- def test():
- time.sleep(2)
- print('test函数执行结束')
- return 123
- # test = timmer(test)
- res = test()
- print(res)
结果:test函数执行结束
函数执行的时间2.000626802444458
None //拿不到返回值
因为test()执行的时候,其实执行的是wrapper(),而wrapper()是没有返回值的
所以再来一个改良版,加上返回值的装饰器:
- import time
- def timmer(func):
- def wrapper():
- start_time = time.time()
- res = func() //把test()的返回值保存为res
- end_time = time.time()
- print('函数执行的时间%s' %(end_time - start_time))
- return res //执行wrapper()时返回test()的返回值
- return wrapper
- @timmer
- def test():
- time.sleep(2)
- print('test函数执行结束')
- return 123
- # test = timmer(test)
- aaa = test()
- print(aaa)
结果:test函数执行结束
函数执行的时间2.0007336139678955
123
上述被修饰函数test()是没有参数的,如果是带参数的怎么办?
- import time
- def timmer(func):
- def wrapper(name,age): //wrapper()需要加上被修饰函数的参数,因为test('nick',25)实际上是在执行wrapper('nick',25),不加会报错
- start_time = time.time()
- res = func(name,age) //这里才是实际执行test()函数,所以也要加上参数
- end_time = time.time()
- print('函数执行的时间%s' %(end_time - start_time))
- return res
- return wrapper
- @timmer # test = timmer(test)
- def test(name,age):
- time.sleep(2)
- print('test函数执行结束' ' 姓名%s' ' 年龄%s'%(name,age))
- return 123
- aaa = test('nick',25)
- print(aaa)
结果:test函数执行结束 姓名nick 年龄25
函数执行的时间2.000373125076294
123
又有一个新问题,被修饰函数的参数个数是不固定的,如果再来一个test2()有三个参数,就无法被timmer()装饰
继续改良升级版,加上返回值+参数的装饰器
- import time
- def timmer(func):
- def wrapper(*args,**kwargs): //wrapper()接受任意数量的参数
- start_time = time.time()
- res = func(*args,**kwargs) //把wrapper()接受的参数原封不动传给test()
- end_time = time.time()
- print('函数执行的时间%s' %(end_time - start_time))
- return res
- return wrapper
- @timmer # test = timmer(test)
- def test(name,age):
- time.sleep(2)
- print('test函数执行结束' ' 姓名%s' ' 年龄%s'%(name,age))
- return 123
- aaa = test('nick',25)
- print(aaa)
- 结果:test函数执行结束 姓名nick 年龄25
函数执行的时间2.0007336139678955
123
到此,不修改被修饰函数的源代码+不修改被修饰函数的调用方式+新增附加功能+加参数+有返回值的装饰器完成。
最后来一个例子,模拟电商系统的用户认证与session会话保持的例子:
- #当前已注册的用户 //一般是数据库,记录已存在的用户信息,这里用list代替
- user_list=[
- {'name':'nick','passwd':'123'},
- {'name':'xiaodong','passwd':'123'},
- {'name':'xiaohu','passwd':'123'},
- {'name':'xiaowang','passwd':'123'},
- ]
- #当前的登录状态 //这里就是记录session的登录状态
- nowlogin_dic = {'usrname':None,'login':False}
- def vali(login_type='jdbc'): #为了传一个参数,判断哪一种认证方式,默认为jdbc数据库
- def validate(func):
- def wrapper(*args,**kwargs):
- if login_type =='jdbc':
- if nowlogin_dic['usrname'] and nowlogin_dic['login']:
- res = func(*args, **kwargs)
- return res
- username = input('请输入用户名:').strip()
- passwd = input('请输入密码:').strip()
- for user_dic in user_list:
- if username == user_dic['name'] and passwd == user_dic['passwd']:
- nowlogin_dic['usrname'] = username
- nowlogin_dic['login'] = True
- res = func(*args, **kwargs)
- return res
- else:
- print('用户名密码错误')
- elif login_type == 'ldap':
- print('ldap都通过认证')
- res = func(*args, **kwargs)
- return res
- else:
- print('不通过!!!')
- res = func(*args, **kwargs)
- return res
- return wrapper
- return validate
- @vali() #index = validate(index)
- def index(): #不传参数就默认认证方式为jdbc
- print('欢迎来到主页')
- @vali(login_type='ldap') //这一步是关键,@vali(login_type='ldap')返回一个validate,所以就相当于@validate,即home = validate(home) ----这么做就是在实际的装饰器validate外面套了一层,为了多一个参数来区别认证方式
- def home():
- print('欢迎回家')
- @vali(login_type='haha')
- def shopping_car():
- print('购物车里有 %s %s %s' % ('茶杯', '牛奶', '香蕉'))
- index()
- home()
- shopping_car()
结果:
请输入用户名:nick
请输入密码:123
欢迎来到主页
ldap都通过认证
欢迎回家
不通过!!!
购物车里有 茶杯 牛奶 香蕉
(十)Python装饰器的更多相关文章
- Python第二十六天 python装饰器
Python第二十六天 python装饰器 装饰器Python 2.4 开始提供了装饰器( decorator ),装饰器作为修改函数的一种便捷方式,为工程师编写程序提供了便利性和灵活性装饰器本质上就 ...
- 你必须学写 Python 装饰器的五个理由
你必须学写Python装饰器的五个理由 ----装饰器能对你所写的代码产生极大的正面作用 作者:Aaron Maxwell,2016年5月5日 Python装饰器是很容易使用的.任何一个会写Pytho ...
- 遥想大肠包小肠----python装饰器乱弹
说起装饰器就tm蛋疼,在老男孩学习python装饰器,结果第二天默写,全错了,一道题抄十遍,共计二十遍. 要是装饰器是一人,我非要约他在必图拳馆来一场...... 下面容我展示一下默写二十遍的成果 语 ...
- Python装饰器、迭代器&生成器、re正则表达式、字符串格式化
Python装饰器.迭代器&生成器.re正则表达式.字符串格式化 本章内容: 装饰器 迭代器 & 生成器 re 正则表达式 字符串格式化 装饰器 装饰器是一个很著名的设计模式,经常被用 ...
- 关于python装饰器
关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...
- python装饰器通俗易懂的解释!
1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...
- Python 装饰器学习
Python装饰器学习(九步入门) 这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...
- python 装饰器修改调整函数参数
简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...
- python 装饰器学习(decorator)
最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...
随机推荐
- DBeaver连接MySQ报错
遇错情况:第一次使用DBaver连接MySQL遇到以下问题: 报错信息:Public Key Retrieval is not allowed 截图如下: 解决方案步骤: 一.已有连接的情况:F4或者 ...
- Vue-组件化,父组件传子组件常见传值方式
前言 我们都知道vue核心之一:组件化,vue中万物皆组件,组件化我认为应该来至于模块化的设计思想,比如在模块化开发中,一个模块就是一个实现特定功能的独立的文件,有了模块我们就更方便去阅读代码,更方便 ...
- Linux下查看目录文件大小
1.ls -lht 查看当前目录下文件的大小 2.du -sh 查看当前文件夹的大小
- 阿里不允许使用 Executors 创建线程池!那怎么使用,怎么监控?
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 五常大米好吃! 哈哈哈,是不你总买五常大米,其实五常和榆树是挨着的,榆树大米也好吃, ...
- [日常摸鱼]bzoj3122 [Sdoi]2013 随机数生成器
又是写了一晚上才过的题- 题意:有一个数列$x_n=(ax_{n-1}+b) mod p$,给你$x_1,a,b,p,t$,求最小的$x_i=t$的$i$,可能不存在 一开始很自然的推出了式子$x_n ...
- Eureka系列(五) 服务续约流程具体实现
服务续约执行简要流程图 下面这张图大致描述了服务续约从Client端到Server端的大致流程,详情如下: 服务续约Client源码分析 我们先来看看服务续约定时任务的初始化.那我们的服务续约 ...
- Ecshop V2.7代码执行漏洞分析
0x01 此漏洞形成是由于未对Referer的值进行过滤,首先导致SQL注入,其次导致任意代码执行. 0x02 payload: 554fcae493e564ee0dc75bdf2ebf94caads ...
- Xrdp远程连接到CentOS7系统配置
1 服务器端配置 1.1 查询是否已经安装epel库 打开已经安装了CentOS7的主机,以root用户登录,在桌面上打开一个终端,输入命令:rpm -qa|grep epel,查询 ...
- 搜索引擎优化(SEO)解决方案
搜索引擎优化(SEO)解决方案 在此之前,希望大家能重新审视搜索引擎,通俗来讲就是我们日常所用的百度.谷歌.搜狗.雅虎等.磨刀不误砍柴工,知己知彼,百战不殆! 一.搜索引擎是什么? 搜索引擎(Se ...
- ARP局域网断网攻击
Kali--ARP局域网攻击 什么是ARP? ARP ( Address Resolution Protocol)地址转换协议,工作在OSI模型的数据链路层,在以太网中,网络设备之间互相通信是用MAC ...