Python面向函数编程——装饰器的实际应用
来源:互联网 发布:excel软件官方下载 编辑:程序博客网 时间:2024/05/21 00:18
转载来源:http://1.python123.sinaapp.com/?p=67 所有版权归原作者所有
对某个函数使用了装饰器之后,实际上是改变了函数的代码入口点。也就是变成了装饰器函数所返回的函数的代码入口点了。
机制
装饰器之所以能够工作,是因为Python是一个动态语言。函数是作为第一级对象存在的,就是说函数可以存储到变量中、作为参数传递给其他函数,最重要的是能够被函数动态地创建和返回。
而Python的装饰器就是一个函数,这个函数可以是内置的(比如@staticmethon、@classmethon),也可以是自定义的函数。这个函数接收另一个函数作为参数(也就是要被装饰的函数)。装饰器把被装饰的函数作为参数,然后充当装饰器的函数内可以有任意的操作,只要保证返回一个可执行的函数就可以了(这个函数既可以是被装饰的函数,也可以是一个全新的函数),可以想象其作用的强大了。
错误的装饰器
看看下面这个代码:
12345678910
def decorator1(fn): print ('other operation 1') return fn @decorator1def test1(): print ('hello test1') test1()test1()
输出
>>>other operation 1hello test1hello test1
这段代码中第5行是需要我们来关注的,Python中的装饰器就是通过@表示的。当执行到这一行时,其实执行的是decorator1(test1)这么个伪代码,于是乎:
1、 执行第二行代码,输出”other operation 1”;
2、 返回一个函数,这个函数恰好就是test1。
第5行代码执行完后,原来的函数test1的入口点就被修改了,不过恰好还是原来的入口点。
接下来执行第9、10行,这会执行装饰后的test1,只不过恰好装饰后的代码和原来的代码完全一样。
这种写法的装饰器(如果能叫做装饰器的话)其实并不是真正意义上的装饰器。因为它并没有给被装饰的函数添加任何的功能。装饰器(decorator1)函数体中的其它操作(print)只会被调用一次,也就是只有在第5行这个地方调用一次。有些网上文章说是【被装饰的函数首次调用时才会执行】,这是误导,你可以把第9、10行的代码去掉,不调用被装饰的函数,仍然会输出”other operation 1”字样。因此虽然两次调用了test(),但其实调用的都是test()自己,并没有任何附加的功能。要想保证其真的添加额外的功能,需要换种写法。
真正的装饰器
装饰器根据是否有参数分成两种:无参数和有参数。
这里所说的参数跟被装饰的函数没有任何关系,被修饰的函数有没有参数并不影响装饰器的分类。先来看无参数的装饰器。
无参数的装饰器
12345678910111213
def decorator2(fn): print('only once') def wrapper(): print('other operation 2') return fn() return wrapper @decorator2def test2(): print ("hello test2") test2()test2()
第8行的执行方式:
1、 进入到decorator2函数入口;
2、 执行第2行,输出”only once”;也就是说这个语句只会执行一次;
3、 执行第3行,这是一个函数定义开始的地方,函数本身就是个对象;因此相当于创建了一个行数对象;
4、 执行到第6行,返回这个函数对象。
现在,test2()函数的入口点就被修改了,原来test2的入口点是第10行,现在变成了第4行了。
第12行的执行方式:
1、 进入test2的入口点,现在这个入口点是第4行了;
2、 执行第4行;
3、 执行第5行,进入test2函数的原来入口点——第10行;
4、 执行第10行。
输出
>>>onle onceother operation 2hello test2other operation 2hello test2
有参数的装饰器
修饰器也支持参数,不过这种修饰器在执行的时候,参数列表中并没有被修饰函数这个参数,因此必须首先返回一个decorator函数,由后者对被修饰的函数做处理。
12345678910111213141516
def decorator3(name): def indecorator(fn): print ('only once') def wrapper(): print ('other operation 3'+name) return fn() return wrapper return indecorator @decorator3('zhangxiaoming') def test3(): print ("hello test3") test3()test3()
第10行代码的执行是这样的:
1、 进入到函数decorator3的入口点(带着参数name,参数值是’zhangxiaoming’);
2、 执行第2行,这是一个函数定义,这才是真正的装饰器函数;
3、 执行第8行,返回这个函数装饰器;
4、 在执行这个函数装饰器,这回才把要装饰的函数test3作为参数传递进去;
5、 执行第4行,输出’only once’;
6、 执行第5行;
7、 执行第7行,返回装饰后的函数。
现在,函数test3的入口点就被修改了,修改后的入口点变成了第5行。
第15的执行是这样的:
1、 进入到test3的入口点,现在这个入口点变成了第5行;
2、 执行第5行;
3、 执行第6行,进入到test3原来的入口点,也就是第13行;
4、 执行第13行。
最终的输出是这样的:
>>>only onceother operation 3zhangxiaominghello test3other operation 3zhangxiaominghello test3
装饰器的作用
装饰器的应用场景是很丰富的,下面就用两个最常见的例子来解释。
身份验证
对于一个快速开发的网站程序,最开始的时候程序员只关注业务的功能,比如浏览、订单等功能,后来用户量上来了,希望加上身份认证的功能。但是对于不同的访问,验证要求又不一样,比如浏览产品功能不需要认证,只有要下订单时才需要认证。
怎么做呢,当然可以打开业务功能的代码,在里面加上认证逻辑,也就是加上一堆if…else…。但这样做势必要修改原来精心测试的代码,这些代码都需要重新测试。麻烦的还不只如此,以后身份认证机制可能会变,比如一开始用户名、密码记录在文本文件中,后来用了数据库,后来网站大了,又加上了SSO等等更复杂的验证方式。问题是,这些验证方法的逻辑只有安全工程师才懂,如果强迫业务程序员在他们的代码中加上这些逻辑会让蛋疼的。最好的方法就是装饰器:
123456789101112131415
#encoding=utf-8#web_mock.pydef GET(): print (u'通过Get方法访问') def POST(name): print (u'通过POST方法访问') print (u'你好 '+name.decode('utf-8')) print('*'*40)GET()print('*'*40)POST('huang')print('*'*40)POST('zhang xiao ming')
这是一个模拟的web页面的代码,这里没有任何身份验证,GET、POST方法完成的都是纯业务的逻辑。代码的输出是:
>>>****************************************通过Get方法访问****************************************通过POST方法访问你好 huang****************************************通过POST方法访问你好 zhang xiao ming
好了,我现在要加上身份认证了,当然我可以直接修改这个业务代码,像这样:
123456789101112131415161718
#encoding=utf-8 def GET(): print (u'通过Get方法访问') def POST(name): if name=='zhang xiao ming': print (u'通过POST方法访问') print (u'你好 '+name.decode('utf-8')) else : print (u'POST方法需要身份认证') print('*'*40)GET()print('*'*40)POST('huang')print('*'*40)POST('zhang xiao ming')
这段代码的输出如下:
>>>****************************************通过Get方法访问****************************************POST方法需要身份认证****************************************通过POST方法访问你好 zhang xiao ming
问题是,我的程序中有大量的类似POST方法都需要加上身份认证,难道让我一个个的改过去吗?真蛋疼啊!
幸运的是,我学会了装饰器,于是我就可以这么做了,新建一个auth_deco.py,其中的代码是这样的:
1234567891011121314151617181920212223
#encoding=utf8#auth_deco.pydef webauth(conf): def _authuser(fname,uname): return uname=='zhang xiao ming' def _noauthuser(fname): print (unicode(fname)+u'需要提供用户名密码') def auth(fn): def wrapper(uname): if _authuser(fn.__name__,uname): fn(uname) else : _noauthuser(fn.__name__) return wrapper def noauth(fn): def wrapper(): return fn() return wrapper return {'needauth':auth,'noauth':noauth}[conf]
现在,使用这个装饰器,web_mock.py中只需要加上三条语句就可以了:
1234567891011121314151617181920
#encoding=utf-8#web_mock.py from auth_deco import webauth @webauth('noauth')def GET(): print (u'通过Get方法访问') @webauth('needauth')def POST(name): print (u'通过POST方法访问') print (u'你好 '+name.decode('utf-8')) print('*'*40)GET()print('*'*40)POST('huang')print('*'*40)POST('zhang xiao ming')
输出是:
>>>****************************************通过Get方法访问****************************************POST需要提供用户名密码****************************************通过POST方法访问你好 zhang xiao ming
再来一个记录日志的例子
这个例子中,被装饰的方法本身可能也有参数。
123456789101112131415161718192021222324252627282930313233343536
#encoding=utf8 def loger(conf): def _log(f,*args,**kargs): print (u""" 记录日志 调用:%s 参数:args(%r) kargs(%r) """ % (f,args,kargs)) def needlog(fn): def wrapper(*args,**kargs): _log(fn.__name__,*args,**kargs) return fn(*args,**kargs) return wrapper def nolog(fn): def wrapper(*args,**kargs): print(u'不用做记录') return fn(*args,**kargs) return wrapper return {'needlog':needlog,'nolog':nolog}[conf] @loger('needlog')def test1(name): print ('hello '+name) @loger('nolog')def test2(name): print ('hello2 '+name) test1('zhang xiao ming')test2('haha haha')
输出
>>> 记录日志 调用:test1 参数:args(('zhang xiao ming',)) kargs({})hello zhang xiao ming不用做记录hello2 haha haha
多个装饰器一起使用
这时候需要注意装饰器的顺序
12345678910111213141516
def head(fn): def wrapper(): return ''+fn()+'' return wrapper def meta(fn): def wrapper(): return ''+fn()+'' return wrapper @head@metadef test(): return 'test' print(test())
输出是:
>>><head><meta>test</meta></head>
- Python面向函数编程——装饰器的实际应用
- python 装饰器的函数式编程
- Python装饰器-面向切面的编程AOP1
- Python装饰器-面向切面的编程AOP2
- 说说Python的装饰器模式与面向切面编程
- Python 装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- Python装饰器与面向切面编程
- 如果说中国程序员技术偏低,原因可能在这里
- 浅谈国内软件公司为何无法做大做强?
- Ubuntu 下启用 Thinkpad 的指纹登录
- Linux环境变量的设置和查看方法
- Linux下的网络编程
- Python面向函数编程——装饰器的实际应用
- Multiple operations have reported errors Select an error to view its details
- 单例实现
- 点名系统
- java反射详解
- Spring框架的模块概要图及各模块功能简介
- 判断直线与矩形相交
- iOS开发:如何使用iOS手势UIGestureRecognizer
- Linux 备忘