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


猜你喜欢
- 在使用SQL Server存储过程或者触发器时,通常会使用自定义异常来处理一些特殊逻辑。例如游标的销毁,事务的回滚。接下来将会详细的介绍SQ
- 程序测试是展现BUG存在的有效方式,但令人绝望的是它不足以展现其缺位。——艾兹格·迪杰斯特拉(Edsger W. Dijkstra)算法审查
- 本文实例讲述了Python实现多并发访问网站功能。分享给大家供大家参考,具体如下:# Filename:visitweb_threads.p
- 1.前言对于数据库引擎来说,内存是一个性能提升的重要解决手段。把数据缓存起来,可以避免在查询或更新数据时花费多余的时间,而这时间通常是从磁盘
- 元素浮动导致的问题及解决办法大家都应该很熟悉了,举个简单的例子:<style type="text/css">
- pytorch中的权值初始化官方论坛对weight-initilzation的讨论torch.nn.Module.apply(fn)torc
- 问题背景在项目开发过程中,我遇到一个需求:对于某条记录,一个用户对它进行操作时会持续比较久,希望在一个用户的操作期间,不允许有另一个用户操作
- python中字符串内置方法很多,可以通过dir()方式查看具体有哪些方法,下表是python字符串的全部的内置方法方法名描述capital
- 1、首先介绍简单的try......except尝试运行的放例如下面的图和代码来简单介绍下:def test(x,y): pri
- 1、修改本地化时间原理: 本地化时间格式化需要gettext支持, 假如你的环境没有开启此功能, 将会返回乱码, 影响#phpmyadmin
- 最近在学习python的内容,在导入requsets库的时候遇到了问题。import requests查了一下资料是requests库需要安
- 网络上有很多关于语言选择的问题,其中关于Python和Java的问题大抵如下:Python和Java该如何选择 ?将来Python(Java
- SQLServer中的批量注释批量注释Ctrl + (K,C):按住Ctrl键不放,然后依次按下K和C批量取消注释Ctrl + (K,U):
- 本篇文章将带你了解报表自动化的流程,并教你用Python实现工作中的一个报表自动化实战,篇幅较长,建议先收藏,文章具体的目录为:1.Exce
- 现在网上有很多python2写的爬虫抓取网页图片的实例,但不适用新手(新手都使用python3环境,不兼容python2),所以我用Pyth
- 项目环境:python3.6一、项目结构二、数据集准备数据集准备分为两步:获取图片.提取人脸.1、获取图片首先可以利用爬虫,从百度图片上批量
- K-Means聚类算法介绍K-Means又称为K均值聚类算法,属于聚类算法中的一种,而聚类算法在机器学习算法中属于无监督学习,在业务中常常会
- 在做机器学习的时候,遇到这样一个数据集...一共399行10列,1-9列是用不定长度的空格分割,第9-10列之间用'\t'分
- 本文教大家调用电脑摄像头进行实时人脸+眼睛识别+微笑识别,供大家参考,具体内容如下一、调用电脑摄像头进行实时人脸+眼睛识别# 调用电脑摄像头
- 1.弹启一个全屏窗口 <html> <body onload="win