Python Decorator的设计模式演绎过程解析

作者:_Zhao 时间:2021-10-13 14:29:37 

关于代理模式、装饰模式

设计模式中经常提到的代理模式、装饰模式,这两种叫法实际上是说的同一件事,只是侧重点有所不同而已。

这两者都是通过在原有对象的基础上封装一层对象,通过调用封装后的对象而不是原来的对象来实现代理/装饰的目的。

例如:(以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

补充

可调用对象包括函数、方法、实现了__call__方法的类,上述内容只是针对函数来解释,对于方法、实现了__call__方法的类,其基本原理相同,具体实现略有差别。

来源:https://segmentfault.com/a/1190000003719779

标签:Python,Decorator,设计模式
0
投稿

猜你喜欢

  • 解决MySQL不允许从远程访问的方法

    2010-03-18 15:39:00
  • vue parseHTML函数解析器遇到结束标签

    2023-07-02 16:56:09
  • pytorch 移动端部署之helloworld的使用

    2022-03-30 18:56:18
  • python中subplot大小的设置步骤

    2021-07-30 08:59:37
  • vuex实现简单的购物车功能

    2024-05-08 10:42:20
  • asp空间判断jmail组件是否安装或支持的代码

    2011-02-16 10:49:00
  • pytorch 中transforms的使用详解

    2021-11-12 04:51:18
  • 详解Go语言如何使用标准库sort对切片进行排序

    2024-04-30 10:07:53
  • python图片合成的示例

    2023-11-17 17:09:36
  • SQL Server数据库对于应用程序的关系

    2010-09-08 09:42:00
  • 带你深入了解Access数据库的4种安全方式

    2008-11-28 14:34:00
  • 浅谈pycharm使用及设置方法

    2023-12-18 21:17:47
  • Python QQBot库的QQ聊天机器人

    2022-03-18 23:29:30
  • Python 程序报错崩溃后如何倒回到崩溃的位置(推荐)

    2021-01-08 16:14:34
  • Python的文本常量与字符串模板之string库

    2022-04-11 05:13:25
  • python使用Plotly绘图工具绘制柱状图

    2021-10-05 00:22:34
  • mysql 让一个存储过程定时作业的代码

    2024-01-26 18:35:28
  • Python中字典的相关操作介绍

    2021-03-16 07:48:54
  • Django 权限认证(根据不同的用户,设置不同的显示和访问权限)

    2023-09-18 04:07:30
  • Python+OpenCV让电脑帮你玩微信跳一跳

    2021-12-16 10:31:17
  • asp之家 网络编程 m.aspxhome.com