作者 | 梁唐  责编 | 张文

头图 | CSDN 下载自东方 IC

来源 | TechFlow(ID:techflow2019)

大家好,今天给大家介绍一个新的设计模式,叫做 memento 模式。

memento 在英文当中是纪念品的意思,在这里,指的是对象的深度拷贝。通过对对象深度拷贝的方法来实现事务的功能。

有了解过数据库的小伙伴应该都知道,在数据库中有些操作是绑定的,要么一起执行成功,要么一起不执行,绝对不允许某些操作执行了,某些操作没有执行的情况发生。这一点就被称为事务。

深度拷贝

我们先来简单回顾一下 Python 当中的拷贝。

拷贝在很多语言当中都有对应的函数,在 Python 当中也不例外。Python 中的拷贝函数有两个,一个是 copy,另外一个是 deepcopy。也就是常说的深拷贝和浅拷贝,这两者的区别也非常简单,简而言之就是浅拷贝只会拷贝父类对象,不会拷贝父类对象当中的子对象。

我们来看一个例子,在下图当中 b 是 a 的浅拷贝,我们可以看到当 a[2] 当中插入了 5 之后,b 当中同样也多了一个 5 。因为它们下标 2 存储的是同一个引用,所以当 a 当中插入的时候,b 当中也发生了同样的改变。我们也可以看到,当我们改变了 a[0] 的时候,b 当中则没有发生对应的改变。因为a[0] 是一个数字,数字是基础类型直接存储的值而不是引用。

与浅拷贝对应的就是深拷贝,我们可以看到,当 a[2] 当中插入元素的时候,深度拷贝出来的 b 并不会发生对应的变化。

memento

利用拷贝,我们可以实现 memento 函数。它的作用是给对象做备份。在Python 当中,对于一个对象 obj 来说,它所有的成员以及函数等信息全是储存在obj.__dict__这个 dict 当中的。也就是说如果我们将一个对象的__dict__拷贝一份的话,其实就相当于我们把对象拷贝了一份。

通过使用拷贝,我们可以很容易实现 memento 函数,先来看代码:

from copy import copy, deepcopy
def memento(obj, deep=False):    state = deepcopy(obj.__dict__) if deep else copy(obj.__dict__)def restore():        obj.__dict__.clear()        obj.__dict__.update(state)        return restore

memento 是一个高阶函数,它返回的结果是执行函数,而不是具体的执行结果。如果对高阶函数不太熟悉的同学,可以去回顾一下 Python 当中高阶函数的相关内容。

这里面的逻辑不难理解,传入的参数是一个 obj 的对象和一个 bool 型的flag。flag 表示使用深拷贝或浅拷贝,obj 就是我们需要做对应快照或者是存档的对象。我们希望在对象框架不变的基础上恢复其中的内容,所以我们拷贝的范围很明确,就是 obj.__dict__,这当中存储了对象的所有关键信息。

我们看下 restore 这个函数,当中的内容其实很简单,只有两行。

第一行是清空 obj 目前__dict__当中的内容,第二步是用之前保存的 state 来还原。其实 restore 执行的是一个回滚 obj 的功能,我们捋一下整个过程。

我们运行 memento 函数会得到 restore 这个函数,当我们执行这个函数的时候,obj 当中的内容会回滚到上次执行 memento 时的状态。

理解了 memento 当中的逻辑之后,距离我们实现事务就不远了。关于事务我们有两种实现方法,一种是通过对象,一种是通过装饰器,我们一个一个来说吧。

Transaction对象

面向对象实现的方式比较简单,它和我们平时使用事务的过程也比较近似。Transaction 对象当中应该提供两个函数,一个是 commit 一个是rollback。也就是说当我们执行成功之后我们执行 commit,对执行的结果进行快照。如果执行失败则 rollback,将对象的结果回滚到上一次 commit时的状态。

我们理解了 memento 函数之后,会发现 commit 和 rollback 刚好对应执行 memento 函数以及执行 restore 函数。这样我们不难写出代码:

class Transaction:deep = False    states = []def __init__(self, deep, *targets):        self.deep = deep        self.targets = targets        self.commit()def commit(self):        self.states = [memento(target, self.deep) for target in self.targets]def rollback(self):        for a_state in self.states:            a_state()

由于我们需要事务的对象可能不止一个,所以这里的 targets 设计成了数组的形式。

Transaction装饰器

我们也可以把事务实现成装饰器,这样我们可以通过注解的方式来使用

这里的代码原理也是一样的,只不过实现逻辑基于装饰器而已。如果对装饰器熟悉的同学,其实也不难理解。这里的 args[0] 其实就是某一个类的实例,也就是我们需要保证事务的主体。

from functools import wraps
def transactional(func):    @wraps(func)    def wrapper(*args, **kwargs):        # args[0] is obj        state = memento(args[0])        try:            func(*args, **kwargs)        except Exception as e:            state()            raise e    return wrapper

这是常规装饰器的写法,当然我们也可以用类来实现装饰器,其实原理差不多,只是有一些细节不太一样。

class Transactional:def __init__(self, method):        self.method = methoddef __get__(self, obj, cls):        def transaction(*args, **kwargs):            state = memento(obj)            try:                return self.method(*args, **kwargs)            except Exception as e:                state()                raise e        return transaction

当我们将这个注解加在某一个类方法上,当我们执行 obj.xxx 的时候,就会执行 Transactional 这个类中的__get__方法,而不是获得 Transactional 这个类。并且把 obj 以及 obj 对应的类型作为参数传入,也就是这里的 obj 和 cls 的含义。这个是用类来实现装饰器的常规做法,我们贴一下常规的代码,来比较学习一下。

class Wrapper:    def __init__(self, func):        wraps(func)(self)def __call__(self, *args, **kwargs):        return self.__wrapped__(*args, **kwargs)def __get__(self, instance, cls):        if instance is None:            return self        else:            return types.MethodType(self, instance)

这是一个用类来实现装饰器的 case,我们可以看到在__get__这个函数当中返回的是 self,也就是返回了 Wrapper 这个类。类通常是不能直接执行的,为了让它能够执行,这里给它实现了一个__call__函数。用类实现装饰器也不常见,我们熟悉高阶函数的方法就可以了。

实战

最后我们来看一个实际应用的例子,我们实现了一个 NumObj 的类,兼容了上面两种事务的使用,可以对比一下看看区别。

class NumObj:    def __init__(self, value):        self.value = valuedef __repr__(self):        return '<%s, %r>' % (self.__class__.__name__, self.value)def increment(self):        self.value += 1@transactional    def do_stuff(self):        self.value += '111'        self.increment()                if __name__ == '__main__':    num_obj = NumObj(-1)a_transaction = Transaction(True, num_obj) # 使用Transaction    try:        for i in range(3):            num_obj.increment()            print(num_obj)a_transaction.commit()        print('----committed')        for i in range(3):            num_obj.increment()            print(num_obj)        num_obj.value += 'x'        print(num_obj)    except Exception:        a_transaction.rollback()        print('----rollback')print(num_obj) # 使用Transactional    print('-- now doing stuff')    num_obj.increment()try:        num_obj.do_stuff()    except Exception:        print('-> doing stuff failed')        import sys        import traceback        traceback.print_exc(file=sys.stdout)print(num_obj)

从代码当中,我们不难发现对于 Transaction 也就是面向对象实现的,我们需要额外创建一个 Transaction 的实例来在 try catch 当中控制是否执行回滚。而使用注解的方式更加灵活,它执行失败会自动执行回滚,不需要太多的额外操作。

一般来说我们更加喜欢使用注解的方式,因为这样的方式更加简洁干净,更加pythonic,能够体现出 Python 的强大。第一种方法显得有些中规中矩,不过好处是可读性强一些代码实现难度也低一些

大家如果在实际工作当中有需要用到,可以根据自己的实际情况去进行选择,两种都是不错的方法。

今天的文章就到这里,衷心祝愿大家每天都有所收获。

更多精彩推荐
☞挑战 Linux 之父认为的“不可能”:向 M1 Mac 移植 Linux☞酷派奖励程序员10 万股期权!因代码贡献受 Linux 之父亲自点名赞赏☞在英雄联盟地图中寻找“数据结构的大门”☞Serverless 如何落地?揭秘阿里核心业务大规模落地实现☞中科大“九章”历史性突破,但实现真正的量子霸权还有多远?
☞云原生应用Go语言:你还在考虑的时候,别人已经应用实践
点分享点点赞点在看

只用两个函数实现事务的设计模式!相关推荐

  1. MySQL 笔记6 -- 函数与事务

    MySQL 笔记6 – 函数与事务 MySQL 系列笔记是笔者学习.实践MySQL数据库的笔记 课程链接: MySQL 数据库基础入门教程 参考文档: MySQL 官方文档 SQL 教程 一.内置函数 ...

  2. 危险的两个函数GetCurrentDirectory和GetParent

    最近这段时间用vc做了一个客户端程序,好长时间不做这种程序了,很是生疏,由于接手的别人的二手项目,大部分代码都已经完成了,只是跑起来问题很大,很多意料之中和意料之外的问题让人又爱又恨.其中遇到了两个函 ...

  3. 文本检查点web_reg_find和web_find两个函数的区别

    LR脚本实战:文本检查点web_reg_find和web_find两个函数的区别 web_reg_find是先注册(register)后查找的:使用时将它放在请求语句的前面. 而web_find是查找 ...

  4. WebDay18 MySQL存储过程 存储函数 触发器 事务

    MySQL存储过程 存储函数 触发器 事务 一.MySQL存储过程和函数 1.存储过程和函数的概念 2.存储过程和函数的好处 3.存储过程和函数的区别 4.创建存储过程 5.调用存储过程 6.查看存储 ...

  5. python如何同时运行两个函数_关于python:使2个函数同时运行

    我试图让两个函数同时运行. 1 2 3 4 5 6 7 8def func1(): print 'Working' def func2(): print 'Working' func1() func2 ...

  6. 倒水问题:给定两个没有刻度的容器,对于任意给定的容积,求出如何只用两个瓶装出L升的水,如果可以,输出步骤,如果不可以,输出no solution

    倒水问题:给定两个没有刻度的容器,对于任意给定的容积,求出如何只用两个瓶装出L升的水,如果可以,输出步骤,如果不可以,输出no solution 这个问题我找了百度,发现没有比较好的代码,有一些只判断 ...

  7. ACMNO.22 C语言-公约公倍2 写两个函数,分别求两个整数的最大公约数和最小公倍数,用主函数调用这两个函数,并输出结果两个整数由键盘输入。 输入 两个数 输出 最大公约数 最小公倍数

    题目描述 写两个函数,分别求两个整数的最大公约数和最小公倍数, 用主函数调用这两个函数,并输出结果两个整数由键盘输入. 输入 两个数 输出 最大公约数 最小公倍数 样例输入 6 15 样例输出 3 3 ...

  8. mongodb mysql 事务_MongoDB数据库两阶段提交实现事务的方法详解 _ 蚂蚁视界

    本文实例讲述了MongoDB数据库两阶段提交实现事务的办法.分享给年夜家供年夜家参考,详细如下: MongoDB数据库中操作单个文档老是原子性的,然而,涉及多个文档的操作,通常被作为一个"事 ...

  9. 获取文件名称的两个函数

    获取文件名称的两个函数 FORM f4_filename  changing c_file. CALL FUNCTION 'KD_GET_FILENAME_ON_F4' EXPORTING mask ...

最新文章

  1. 第二届清华大学iCenter量化策略挑战赛开幕!
  2. 〖Android〗存在多个Android设备时,使用Shell脚本选择一个Android设备
  3. VirtualBox在win10下安装一个manjaro linux操作系统的教程
  4. MongoDB 教程八(结语): 一网打尽当下NoSQL类型、适用场景及使用公司
  5. 手机modem开发(1)---MTK modem NVRAM
  6. UVA 216 - Getting in Line
  7. 网狐荣耀6701/6801服务端 子游戏编译 部署
  8. Apache AB 性能测试
  9. batocera整合包_模擬器作業系統RetroPie更新至4.6,支援Raspberry Pi 4、新增NeoGeo CD模擬功能...
  10. 20. 有效的括号 python
  11. 一步完成 MySQL 向 Redis 迁移
  12. 如何提高BT的下载速度?
  13. php,表单+文本域,增加表单的文本域的html
  14. UNITY 5.2.1 发行说明 中文版
  15. Jan Jürjens-基于模型的安全性系统-UMLChina讲座-音频和幻灯
  16. Linux C报错: /usr/bin/ld: cannot find -ldb
  17. 编程自学网站(赶紧收藏)
  18. 云享 文档协同,开启新的文档协作模式
  19. stm32F103zexx(战舰v1) 移植liteOS
  20. HTML项目心得500字,学习心得体会500字大全(7篇)

热门文章

  1. 补充“为什么Scrum不行”
  2. JavaScript面向对象实现
  3. WebBrowser是IE内置的浏览器控件
  4. 服务器端脚本和客户端脚本
  5. oracle触发器(转载收集)
  6. 安装Logstash
  7. c语言基础知识难点,C语言基础的几个难点解析
  8. 如何在矩池云上查看cudnn版本
  9. java des 加密 字符串_Java使用DES加密字符串
  10. 连接路由器后电脑连不上网_电信光纤猫与无线路由器连接怎么设置【图文教程】...