設計模式,你相信嗎,只用兩個函數實現事務!

你們好,今天給你們介紹一個新的設計模式,叫作memento模式。python

memento在英文當中是記念品的意思,在這裏,指的是對象的深度拷貝。經過對對象深度拷貝的方法來實現事務的功能。有了解過數據庫的小夥伴們應該都知道,在數據庫當中有些操做是綁定的,要麼一塊兒執行成功,要麼一塊兒不執行,絕對不運行某些操做執行了,某些操做沒有執行的狀況發生。這一點就被稱爲事務。web

深度拷貝

咱們先來簡單回顧一下Python當中的拷貝。數據庫

拷貝在不少語言當中都有對應的函數,在Python當中也不例外。Python中的拷貝函數有兩個,一個是copy,另一個是deepcopy。也就是常說的深拷貝和淺拷貝,這二者的區別也很是簡單,簡而言之就是淺拷貝只會拷貝父類對象,不會拷貝父類對象當中的子對象設計模式

咱們來看一個例子,在下圖當中b是a的淺拷貝,咱們能夠看到當a[2]當中插入了5以後,b當中一樣也多了一個5。由於它們下標2存儲的是同一個引用,因此當a當中插入的時候,b當中也發生了一樣的改變。咱們也能夠看到,當咱們改變了a[0]的時候,b當中則沒有發生對應的改變。由於a[0]是一個數字,數字是基礎類型直接存儲的值而不是引用。數組

與淺拷貝對應的就是深拷貝,咱們能夠看到,當a[2]當中插入元素的時候,深度拷貝出來的b並不會發生對應的變化。app

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 = method

    def __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 = value

    def __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的強大。而第一種方法顯得有些中規中矩,不過好處是可讀性強一些,代碼實現難度也低一些。你們若是在實際工做當中有須要用到,能夠根據本身的實際狀況去進行選擇,兩種都是不錯的方法。

今天的文章就到這裏,衷心祝願你們天天都有所收穫。若是還喜歡今天的內容的話,請來一個三連支持吧~(點贊、關注、轉發

相關文章
相關標籤/搜索