转载来源:http://1.python123.sinaapp.com/?p=67 所有版权归原作者所有
对某个函数使用了装饰器之后,实际上是改变了函数的代码入口点。也就是变成了装饰器函数所返回的函数的代码入口点了。
机制
装饰器之所以能够工作,是因为Python是一个动态语言。函数是作为第一级对象存在的,就是说函数可以存储到变量中、作为参数传递给其他函数,最重要的是能够被函数动态地创建和返回。
而Python的装饰器就是一个函数,这个函数可以是内置的(比如@staticmethon、@classmethon),也可以是自定义的函数。这个函数接收另一个函数作为参数(也就是要被装饰的函数)。装饰器把被装饰的函数作为参数,然后充当装饰器的函数内可以有任意的操作,只要保证返回一个可执行的函数就可以了(这个函数既可以是被装饰的函数,也可以是一个全新的函数),可以想象其作用的强大了。
错误的装饰器
看看下面这个代码:
1 2 3 4 5 6 7 8 9 10 | |
输出
>>> 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()自己,并没有任何附加的功能。要想保证其真的添加额外的功能,需要换种写法。
真正的装饰器
装饰器根据是否有参数分成两种:无参数和有参数。
这里所说的参数跟被装饰的函数没有任何关系,被修饰的函数有没有参数并不影响装饰器的分类。先来看无参数的装饰器。
无参数的装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
第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函数,由后者对被修饰的函数做处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
第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等等更复杂的验证方式。问题是,这些验证方法的逻辑只有安全工程师才懂,如果强迫业务程序员在他们的代码中加上这些逻辑会让蛋疼的。最好的方法就是装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
这是一个模拟的web页面的代码,这里没有任何身份验证,GET、POST方法完成的都是纯业务的逻辑。代码的输出是:
>>> **************************************** 通过Get方法访问 **************************************** 通过POST方法访问 你好 huang **************************************** 通过POST方法访问 你好 zhang xiao ming
好了,我现在要加上身份认证了,当然我可以直接修改这个业务代码,像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
这段代码的输出如下:
>>> **************************************** 通过Get方法访问 **************************************** POST方法需要身份认证 **************************************** 通过POST方法访问 你好 zhang xiao ming
问题是,我的程序中有大量的类似POST方法都需要加上身份认证,难道让我一个个的改过去吗?真蛋疼啊!
幸运的是,我学会了装饰器,于是我就可以这么做了,新建一个auth_deco.py,其中的代码是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
现在,使用这个装饰器,web_mock.py中只需要加上三条语句就可以了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
输出是:
>>> **************************************** 通过Get方法访问 **************************************** POST需要提供用户名密码 **************************************** 通过POST方法访问 你好 zhang xiao ming
再来一个记录日志的例子
这个例子中,被装饰的方法本身可能也有参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | |
输出
>>> 记录日志 调用:test1 参数:args(('zhang xiao ming',)) kargs({}) hello zhang xiao ming 不用做记录 hello2 haha haha
多个装饰器一起使用
这时候需要注意装饰器的顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
输出是:
>>> <head><meta>test</meta></head>