DP.20:ステートセーブ・ロードを実現する - Mementoパターン -【Python】
【1】Mementoパターン
Mementoパターンは
書き方。要するに「ステートセーブ・ステートロード(UnDo等も含む)」をできるようにするというもの。
【2】Mementoパターンで使う用語
Mementoパターンには「Mement」「Originator」「Caretaker」という3つの用語が出てくる。
■イメージ図1:OriginatorとMementoの関係
「Originator」が自身の状態・値を「mementoオブジェクト」に吐き出したり、「mementoオブジェクト」を読み込んで自身の状態・値を巻き戻す。
■イメージ図2:Caretakerの役割(ステートセーブ)
プログラム上は、「Caretakerオブジェクト」が「Originatorのメソッド」をコールして、「Mementoオブジェクト」を出力させる。
■イメージ図3:Caretakerの役割(ステートロード)
Caretakerオブジェクトが、MementoオブジェクトをOriginatorに渡しつつ、「Originatorのメソッド」をコールする
【3】利用例
「ZODB(Zope Object Database)」というオブジェクトデータベースがある。python のオブジェクトを永続化して保存できる、トランザクション、アンドゥ可能な履歴機能をサポートしている、という点が特徴。
「Pyramid(ウェブフレームワーク)」など、他のアプリの組み合わせて使われることもある。
ZODBがMementoパターンを使ったソフトウェアの一例といえる。
【4】コンセプトコード
状態を1つだけもたせたOriginatorを使った、簡単なコンセプトコードを書いてみる。具体的には
■mementoを作る
■Originatorの状態を書き換える
■Mementoをロードして元に戻す
というもの。
【コンセプトコード】
import pickle
class Originator:
def __init__(self):
self._state = False # セーブやロードをしたいステート:状態
print(vars(self))
def set_memento(self, memento:bytes):
previous_state = pickle.loads(memento)
print(vars(self))
vars(self).clear() # クラスの属性(メンバ変数)をクリアする
print(vars(self))
vars(self).update(previous_state)
print(vars(self))
def create_memento(self):
return pickle.dumps(vars(self))
def main():
originator = Originator()
memento = originator.create_memento()
originator._state = True
originator.set_memento(memento)
if __name__ == "__main__":
main()
▲今回はオブジェクトの状態を保存するMementoとしてpython固有のデータフォーマットである「pickleオブジェクト」を利用した。
【pickleオブジェクト】
pickleオブジェクトについては以下参照。簡単に言うとpython固有のデータフォーマット(バイトデータ)。
▲「赤枠で警告事項」が書かれていてひるむかもしれない。ざっくりいうと、『ネット上から拾ったもの、他人からもらったpickleオブジェクトなどを安易に読み込まないこと。』ということ。
これは、pickleオブジェクトはバイトデータ群なので悪意のあるプログラムが何かしら仕込まれていると実行されてしまうからである。
■Originatorへのデータセットの仕方
Originatorへのデータ再セット方法として今回は「vars()」と「update()」をつかって Originatorクラス内の「__dict__ 属性」(今回の場合は、self._state)を上書きしている。
【5】例題:Quote(名言・格言)管理・出力プログラム
誰かが話したり、書いたりした名言・格言を一時的に記憶しておき、修正したり元に戻すプログラムをつくってみる。
名言・格言を保存しておくオブジェクトは次の通りにする。
【Quoteクラスオブジェクト】
import pickle
from dataclasses import dataclass #dataclassで__init__()は自動生成
@dataclass
class Quote:
text:str
author:str
# ステートセーブ
def save_state(self):
current_state = pickle.dumps(self.__dict__) # データダンプ
return current_state # ダンプされたバイトデータを返す
# ステートロード
def restore_state(self, memento):
previous_state = pickle.loads(memento) # ダンプされたデータをロード
print(self.__dict__)
self.__dict__.clear() # クラス変数をクリア
print(self.__dict__)
self.__dict__.update(previous_state) # クラス変数をロードしたダンプデータから更新
print(self.__dict__)
def __str__(self):
return f"{self.text} by {self.author}"
▲今回はPickleオブジェクトとしてダンプしたデータは、外部ファイルとして書き出していない。
「save_state()」でPickleオブジェクトのバイトデータにしてメモリ上に用意しておき、すぐプログラム上から「restore_state()」で戻せるようなプログラムになっている。
【6】全体コード
import pickle
from dataclasses import dataclass
@dataclass
class Quote:
text:str
author:str
def save_state(self):
current_state = pickle.dumps(self.__dict__)
return current_state
def restore_state(self, memento):
previous_state = pickle.loads(memento)
print(self.__dict__)
self.__dict__.clear()
print(self.__dict__)
self.__dict__.update(previous_state)
print(self.__dict__)
def __str__(self):
return f"{self.text} by {self.author}"
def main():
print("Quote 1")
# Unknown authorとして名言を登録
q1 = Quote("A room without books is like a body without a soul.", 'Unknown author')
print(f'\nOriginal version:\n{q1}')
q1_mem = q1.save_state() # ステートセーブ
# 値の書き換え
q1.author = 'Marcus Tullius Cicero'
print(f'\nWe found the author, and did an updated:\n{q1}')
print("")
# ステートロード(書き換えたauthor部分がもとにもどる)
q1.restore_state(q1_mem)
# q1オブジェクト内の値がUnknown authorにもどっていることを確認する
print(f'\nWe had to restore the previous version:\n{q1}')
print("-------------------")
if __name__ == '__main__':
main()
【実行結果】
もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。