【转】【python】装饰器的原理
写在前面:
在开发OpenStack过程中,经常可以看到代码中的各种注解,自己也去查阅了资料,了解了这是python中的装饰器,因为弱类型的语言可以将函数当成返回值返回,这就是装饰器的原理。
虽然说知道装饰器的使用方法以及原理,但是一直不明白为什么要通过在内部函数返回一个函数名这样的写法,在微信上看到下面这篇文章,豁然开朗。因为觉得写的非常好,所以我也没必要再来写一遍了,直接转载,供以后的开发中参考。
-----------------------------------------------分割线--------------------------------------------------------------
文章链接: https://segmentfault.com/a/1190000003719779
引言
本文主要梳理了Python decorator的实现思路,解释了为什么Python decorator是现在这个样子。
关于代理模式、装饰模式
设计模式中经常提到的代理模式、装饰模式,这两种叫法实际上是说的同一件事,只是侧重点有所不同而已。
这两者都是通过在原有对象的基础上封装一层对象,通过调用封装后的对象而不是原来的对象来实现代理/装饰的目的。
例如:(以Java为例)
public class CountProxy implements Count {
private CountImpl countImpl; public CountProxy(CountImpl countImpl) {
this.countImpl = countImpl;
} @Override
public void queryCount() {
System.out.println("事务处理之前");
// 调用委托类的方法;
countImpl.queryCount();
System.out.println("事务处理之后");
} @Override
public void updateCount() {
System.out.println("事务处理之前");
// 调用委托类的方法;
countImpl.updateCount();
System.out.println("事务处理之后"); } }
在这个例子中CountProxy
是对CountImpl
的封装。
使用者通过CountProxy.queryCount
方法来调用CountImpl.queryCount
方法,这被称为代理,即CountProxy
是代理类,CountImpl
是被代理类。
在CountProxy.queryCount
方法中,可以在CountImpl.queryCount
方法调用之前和之后添加一些额外的操作,被称为装饰,即CountProxy
是装饰类,CountImpl
是被装饰类。
如果强调通过CountProxy
对CountImpl
进行代理的作用,则称为代理模式;
如果强调通过CountProxy
对CountImpl
增加额外的操作,则称为装饰模式;
不论是哪种称呼,其本质都在于对原有对象的封装。
其封装的目的在于增强所封装对象的功能或管理所封装的对象。
从上面的例子也可以发现,代理/封装所围绕的核心是可调用对象(比如函数)。
Python中的代理/装饰
Python中的可调用对象包括函数、方法、实现了__call__方法的类。
Python中的函数也是对象,可以作为高阶函数的参数传入或返回值返回。
因此,当代理/装饰的对象是函数时,可以使用高阶函数来对某个函数进行封装。
例如:
def query_count_proxy(fun, name, age):
print('do something before')
rv = fun(name, age)
print('do something after')
return rv def query_count(name, age):
print('name is %s, age is %d' % (name, age)) query_count_proxy(query_count, 'Lee', 20)
但是,这个例子中,query_count
函数作为参数传入query_count_proxy
函数中,并在query_count_proxy
函数中被调用,其结果作为返回值返回。这就完成了代理的功能,同时,在调用query_count
函数的前后,我们还增加了装饰代码。
但是,query_count_proxy
的函数参数与query_count
不一样了,理想的代理应该保持接口一致才对。
为了保持一致,我们可以利用高阶函数可以返回函数的特点来完成:
def query_count_proxy(fun): def wrapper(name, age):
print('do something before')
rv = fun(name, age)
print('do something after')
return rv return wrapper def query_count(name, age):
print('name is %s, age is %d' % (name, age)) query_count_proxy(query_count)('Lee', 20)
修改后的例子,query_count_proxy
仅负责接受被代理的函数query_count
作为参数,同时,返回一个函数对象wrapper
作为返回值,真正的封装动作在wrapper
这个函数中完成。
此时,如果调用query_count_proxy(query_count)
就得到了wrapper
函数对象,则,执行query_count_proxy(query_count)('Lee', 20)
就相当于执行了wrapper('Lee', 20)
。
但是可以看到,query_count_proxy(query_count)('Lee', 20)
这种使用方法,仍然不能保证一致。
为了保持一致,我们需要利用Python中对象与其名称可以动态绑定的特点。
不使用query_count_proxy(quer_count)('Lee', 20)
来调用代理函数,而是使用下面两句:
query_count = query_count_proxy(query_count)
query_count('Lee', 20)
执行query_count_proxy(query_count)
生成wrapper
函数对象,将这个对象通过query_count = query_count_proxy(query_count)
绑定到query_count
这个名字上来,这样执行query_count('Lee', 20)
时,其实执行的是wrapper('Lee', 20)
。
这么做的结果就是:使用代理时调用query_count('Lee', 20)
与不使用代理时调用query_count('Lee', 20)
对使用者而言保持不变,不用改变代码,但是在真正执行时,使用的是代理/装饰后的函数。
这里,基本利用Python的高阶函数及名称绑定完成了代理/装饰的功能。
还有什么不理想的地方呢?
对,就是query_count = query_count_proxy(query_count)
,因为这句既不简洁,又属于重复工作。
Python为我们提供了语法糖来完成这类的tedious work。
方法就是:
@query_count_proxy
def query_count(name, age):
return 'name is %s, age is %d' % (name, age)
query_count = query_count_proxy(query_count)
就等同于在定义query_count
函数的时候,在其前面加上@query_count_proxy
。
Python看到这样的语法,就会自动的执行query_count = query_count_proxy(query_count)
进行name rebinding
补充
以上就是Python实现可调用对象装饰的核心。
可调用对象包括函数、方法、实现了__call__方法的类,上述内容只是针对函数来解释,对于方法、实现了__call__方法的类,其基本原理相同,具体实现略有差别。
【转】【python】装饰器的原理的更多相关文章
- python装饰器的原理
装饰器的原理就是利用<闭包函数>来实现,闭包函数的原理就是包含内层函数的return和外层环境变量:
- 【低门槛 手把手】python 装饰器(Decorators)原理说明
本文目的是由浅入深地介绍python装饰器原理 装饰器(Decorators)是 Python 的一个重要部分 其功能是,在不修改原函数(类)定义代码的情况下,增加新的功能 为了理解和实现装饰器,我们 ...
- Python装饰器详解
python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...
- 粗浅聊聊Python装饰器
浅析装饰器 通常情况下,给一个对象添加新功能有三种方式: 直接给对象所属的类添加方法: 使用组合:(在新类中创建原有类的对象,重复利用已有类的功能) 使用继承:(可以使用现有类的,无需重复编写原有类进 ...
- python 装饰器、递归原理、模块导入方式
1.装饰器原理 def f1(arg): print '验证' arg() def func(): print ' #.将被调用函数封装到另外一个函数 func = f1(func) #.对原函数重新 ...
- 关于python装饰器
关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...
- Python装饰器由浅入深
装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...
- Python装饰器模式学习总结
装饰器模式,重点在于装饰.装饰的核心仍旧是被装饰对象. 类比于Java编程的时候的包装模式,是同样的道理.虽然概念上稍有不同但是原理上还是比较相近的.下面我就来谈一谈我对Python的装饰器的学习的一 ...
- Python 装饰器(Decorator)
装饰器的语法为 @dec_name ,置于函数定义之前.如: import atexit @atexit.register def goodbye(): print('Goodbye!') print ...
随机推荐
- js 拖动滑块验证
备注:拖动滑块时尽量平移,chrome浏览器上没有卡顿情况,但是搜狗极速模式和360极速模式都遇到了卡顿,拖不动情况,应是浏览器内部对事件响应速度导致吧. JS代码: ;(function ($,wi ...
- 【poj3621】最优比率环
题意: 给定n个点,每个点有一个开心度F[i],每个点有m条单向边,每条边有一个长度d,要求一个环,使得它的 开心度的和/长度和 这个比值最大.n<=1000,m<=5000 题解: 最优 ...
- 【NOIP】提高组2012 借教室
[算法]线段树||二分+前缀和 [题解]线段树记录区间加值和区间最大值. #include<cstdio> #include<algorithm> using namespac ...
- Vue前端开发规范(山东数漫江湖)
一.强制 1. 组件名为多个单词 组件名应该始终是多个单词的,根组件 App 除外. 正例: export default { name: 'TodoItem', // ... } 反例: expor ...
- 最短路之spfa系列
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2544 Problem Description 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t ...
- Morley's Theorem (计算几何基础+向量点积、叉积、旋转、夹角等+两直线的交点)
题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem ...
- HDU 2717 Catch That Cow (深搜)
题目链接 Problem Description Farmer John has been informed of the location of a fugitive cow and wants t ...
- hdu 2717 Catch That Cow(广搜bfs)
题目链接:http://i.cnblogs.com/EditPosts.aspx?opt=1 Catch That Cow Time Limit: 5000/2000 MS (Java/Others) ...
- hdu 2680 Choose the best route (dijkstra算法 最短路问题)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2680 Choose the best route Time Limit: 2000/1000 MS ( ...
- 1002: 当不成勇者的Water只好去下棋了---课程作业---图的填色
1002: 当不成勇者的Water只好去下棋了 Time Limit: 1 Sec Memory Limit: 128 MB Description 由于魔王BOSS躲起来了,说好要当勇者的Wate ...