只用两个函数实现事务的设计模式!
作者 | 梁唐 责编 | 张文
头图 | 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语言:你还在考虑的时候,别人已经应用实践
点分享点点赞点在看
只用两个函数实现事务的设计模式!相关推荐
- MySQL 笔记6 -- 函数与事务
MySQL 笔记6 – 函数与事务 MySQL 系列笔记是笔者学习.实践MySQL数据库的笔记 课程链接: MySQL 数据库基础入门教程 参考文档: MySQL 官方文档 SQL 教程 一.内置函数 ...
- 危险的两个函数GetCurrentDirectory和GetParent
最近这段时间用vc做了一个客户端程序,好长时间不做这种程序了,很是生疏,由于接手的别人的二手项目,大部分代码都已经完成了,只是跑起来问题很大,很多意料之中和意料之外的问题让人又爱又恨.其中遇到了两个函 ...
- 文本检查点web_reg_find和web_find两个函数的区别
LR脚本实战:文本检查点web_reg_find和web_find两个函数的区别 web_reg_find是先注册(register)后查找的:使用时将它放在请求语句的前面. 而web_find是查找 ...
- WebDay18 MySQL存储过程 存储函数 触发器 事务
MySQL存储过程 存储函数 触发器 事务 一.MySQL存储过程和函数 1.存储过程和函数的概念 2.存储过程和函数的好处 3.存储过程和函数的区别 4.创建存储过程 5.调用存储过程 6.查看存储 ...
- python如何同时运行两个函数_关于python:使2个函数同时运行
我试图让两个函数同时运行. 1 2 3 4 5 6 7 8def func1(): print 'Working' def func2(): print 'Working' func1() func2 ...
- 倒水问题:给定两个没有刻度的容器,对于任意给定的容积,求出如何只用两个瓶装出L升的水,如果可以,输出步骤,如果不可以,输出no solution
倒水问题:给定两个没有刻度的容器,对于任意给定的容积,求出如何只用两个瓶装出L升的水,如果可以,输出步骤,如果不可以,输出no solution 这个问题我找了百度,发现没有比较好的代码,有一些只判断 ...
- ACMNO.22 C语言-公约公倍2 写两个函数,分别求两个整数的最大公约数和最小公倍数,用主函数调用这两个函数,并输出结果两个整数由键盘输入。 输入 两个数 输出 最大公约数 最小公倍数
题目描述 写两个函数,分别求两个整数的最大公约数和最小公倍数, 用主函数调用这两个函数,并输出结果两个整数由键盘输入. 输入 两个数 输出 最大公约数 最小公倍数 样例输入 6 15 样例输出 3 3 ...
- mongodb mysql 事务_MongoDB数据库两阶段提交实现事务的方法详解 _ 蚂蚁视界
本文实例讲述了MongoDB数据库两阶段提交实现事务的办法.分享给年夜家供年夜家参考,详细如下: MongoDB数据库中操作单个文档老是原子性的,然而,涉及多个文档的操作,通常被作为一个"事 ...
- 获取文件名称的两个函数
获取文件名称的两个函数 FORM f4_filename changing c_file. CALL FUNCTION 'KD_GET_FILENAME_ON_F4' EXPORTING mask ...
最新文章
- 第二届清华大学iCenter量化策略挑战赛开幕!
- 〖Android〗存在多个Android设备时,使用Shell脚本选择一个Android设备
- VirtualBox在win10下安装一个manjaro linux操作系统的教程
- MongoDB 教程八(结语): 一网打尽当下NoSQL类型、适用场景及使用公司
- 手机modem开发(1)---MTK modem NVRAM
- UVA 216 - Getting in Line
- 网狐荣耀6701/6801服务端 子游戏编译 部署
- Apache AB 性能测试
- batocera整合包_模擬器作業系統RetroPie更新至4.6,支援Raspberry Pi 4、新增NeoGeo CD模擬功能...
- 20. 有效的括号 python
- 一步完成 MySQL 向 Redis 迁移
- 如何提高BT的下载速度?
- php,表单+文本域,增加表单的文本域的html
- UNITY 5.2.1 发行说明 中文版
- Jan Jürjens-基于模型的安全性系统-UMLChina讲座-音频和幻灯
- Linux C报错: /usr/bin/ld: cannot find -ldb
- 编程自学网站(赶紧收藏)
- 云享 文档协同,开启新的文档协作模式
- stm32F103zexx(战舰v1) 移植liteOS
- HTML项目心得500字,学习心得体会500字大全(7篇)