本文主要梳理了Python decorator的实现思路,解释了为什么Python decorator是现在这个样子。 关于代理模式、装饰模式设计模式中经常提到的代理模式、装饰模式,这两种叫法实际上是说的同一件事,只是侧重点有所不同而已。 这两者都是 通过在原有对象的基础上封装一层对象,通过调用封装后的对象而不是原来的对象来实现代理/装饰的目的 。 例如:(以Java为例) public class CountProxy implements Count { privateCountImplcountImpl; publicCountProxy(CountImplcountImpl) { this.countImpl = countImpl; } @Override publicvoidqueryCount() { System.out.println("事务处理之前"); // 调用委托类的方法; countImpl.queryCount(); System.out.println("事务处理之后"); } @Override publicvoidupdateCount() { 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中的函数也是对象,可以作为高阶函数的参数传入或返回值返回。 因此,当代理/装饰的对象是函数时,可以使用高阶函数来对某个函数进行封装。 例如: defquery_count_proxy(fun, name, age): print('do something before') rv = fun(name, age) print('do something after') return rv defquery_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 不一样了,理想的代理应该保持接口一致才对。 为了保持一致,我们可以利用高阶函数可以返回函数的特点来完成: defquery_count_proxy(fun): defwrapper(name, age): print('do something before') rv = fun(name, age) print('do something after') return rv return wrapper defquery_count(name, age): print('name is %s, age is %d' % (name, age)) query_count_proxy(query_count)('Lee', 20) 修改后的例子, query_count_proxy 仅负责接受被代理的函数q uery_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 defquery_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__方法的类,其基本原理相同,具体实现略有差别。 (责任编辑:好模板) |