python装饰器二


装饰器本质:

对某个函数使用了装饰器之后,实际上是改变了函数的代码入口点。也就是变成了装饰器函数所返回的函数的代码入口点了。

机制

装饰器之所以能够工作,是因为Python是一个动态语言。函数是作为第一级对象存在的,就是说函数可以存储到变量中、作为参数传递给其他函数,最重要的是能够被函数动态地创建和返回。

而Python的装饰器就是一个函数,这个函数可以是内置的(比如@staticmethon、@classmethon),也可以是自定义的函数。这个函数接收另一个函数作为参数(也就是要被装饰的函数)。装饰器把被装饰的函数作为参数,然后充当装饰器的函数内可以有任意的操作,只要保证返回一个可执行的函数就可以了(这个函数既可以是被装饰的函数,也可以是一个全新的函数),可以想象其作用的强大了。

 

错误装饰器:

def decorator1(fn):
    print ('other operation 1') 
    return fn 

@decorator1 
def test1(): 
    print ('hello test1') 

test1()
test1() 

输出

>>>
other operation 1
hello test1
hello 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()自己,并没有任何附加的功能。要想保证其真的添加额外的功能,需要换种写法。

 

真正的装饰器

装饰器根据是否有参数分成两种:无参数和有参数。

这里所说的参数跟被装饰的函数没有任何关系,被修饰的函数有没有参数并不影响装饰器的分类。先来看无参数的装饰器。

 

无参数的装饰器

 

def decorator2(fn):
    print('only once') 
    def wrapper(): 
        print('other operation 2') 
        return fn() 
    return wrapper 

@decorator2 
def 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 once
other operation 2
hello test2
other operation 2
hello test2

有参数的装饰器

修饰器也支持参数,不过这种修饰器在执行的时候,参数列表中并没有被修饰函数这个参数,因此必须首先返回一个decorator函数,由后者对被修饰的函数做处理。

 

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 once
other operation 3zhangxiaoming
hello test3
other operation 3zhangxiaoming
hello test3

 

装饰器的作用

装饰器的应用场景是很丰富的,下面就用两个最常见的例子来解释。

身份验证

对于一个快速开发的网站程序,最开始的时候程序员只关注业务的功能,比如浏览、订单等功能,后来用户量上来了,希望加上身份认证的功能。但是对于不同的访问,验证要求又不一样,比如浏览产品功能不需要认证,只有要下订单时才需要认证。

怎么做呢,当然可以打开业务功能的代码,在里面加上认证逻辑,也就是加上一堆if…else…。但这样做势必要修改原来精心测试的代码,这些代码都需要重新测试。麻烦的还不只如此,以后身份认证机制可能会变,比如一开始用户名、密码记录在文本文件中,后来用了数据库,后来网站大了,又加上了SSO等等更复杂的验证方式。问题是,这些验证方法的逻辑只有安全工程师才懂,如果强迫业务程序员在他们的代码中加上这些逻辑会让蛋疼的。

最好的方法就是装饰器:

 

#encoding=utf-8 
#web_mock.py 

def 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

好了,我现在要加上身份认证了,当然我可以直接修改这个业务代码,像这样:

  

#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') 

 

问题是,我的程序中有大量的类似POST方法都需要加上身份认证,难道让我一个个的改过去吗?真蛋疼啊!

幸运的是,我学会了装饰器,于是我就可以这么做了,新建一个auth_deco.py,其中的代码是这样的:

#encoding=utf8 
#auth_deco.py 

def 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中只需要加上三条语句就可以了:

#encoding=utf-8 
#web_mock.py from auth_decoimport 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

再来一个记录日志的例子
这个例子中,被装饰的方法本身可能也有参数。

#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

 

 

多个装饰器一起使用这时候需要注意装饰器的顺序

def head(fn):
    def wrapper():
        return ''+fn()+''
    return wrapper 

def meta(fn):
    def wrapper():
        return ''+fn()+''
    return wrapper 

@head 
@meta 
def test():
    return 'test' 

print(test()) 


输出是:

>>>

<head><meta>test</meta></head>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值