BehavioralPythonverifiedVerified
Memento Pattern in Python
Captures and externalizes an object's internal state without violating encapsulation, allowing the object to be restored to that state later.
How to Implement the Memento Pattern in Python
1Step 1: Define the Memento that captures editor state
from dataclasses import dataclass
@dataclass(frozen=True)
class EditorMemento:
content: str
cursor: int2Step 2: Implement the Editor with save and restore
class Editor:
def __init__(self) -> None:
self._content = ""
self._cursor = 0
def type_text(self, text: str) -> None:
self._content = (
self._content[: self._cursor]
+ text
+ self._content[self._cursor :]
)
self._cursor += len(text)
def save(self) -> EditorMemento:
return EditorMemento(self._content, self._cursor)
def restore(self, memento: EditorMemento) -> None:
self._content = memento.content
self._cursor = memento.cursor
def __str__(self) -> str:
return f'"{self._content}" (cursor: {self._cursor})'3Step 3: Create a History caretaker to manage snapshots
class History:
def __init__(self) -> None:
self._mementos: list[EditorMemento] = []
def push(self, memento: EditorMemento) -> None:
self._mementos.append(memento)
def pop(self) -> EditorMemento | None:
return self._mementos.pop() if self._mementos else None4Step 4: Take snapshots and undo changes
editor = Editor()
history = History()
history.push(editor.save())
editor.type_text("Hello")
history.push(editor.save())
editor.type_text(", World")
print(editor) # "Hello, World" (cursor: 12)
editor.restore(history.pop())
print(editor) # "Hello" (cursor: 5)
editor.restore(history.pop())
print(editor) # "" (cursor: 0)"""Configuration Snapshots with metadata, tags, and search."""
import copy
import time
import uuid
from dataclasses import dataclass, field
from typing import Any, Generic, TypeVar
T = TypeVar("T")
# [step] Define the configuration snapshot and memento
@dataclass(frozen=True)
class ConfigSnapshot(Generic[T]):
id: str
label: str
state: T
created_at: float
tags: tuple[str, ...]
class ConfigMemento(Generic[T]):
def __init__(
self, state: T, label: str, tags: list[str] | None = None
) -> None:
self.id = str(uuid.uuid4())
self.created_at = time.time()
self.label = label
self.tags = list(tags or [])
self._state = copy.deepcopy(state)
def get_snapshot(self) -> ConfigSnapshot[T]:
return ConfigSnapshot(
id=self.id,
label=self.label,
state=copy.deepcopy(self._state),
created_at=self.created_at,
tags=tuple(self.tags),
)
# [step] Implement the caretaker with search capabilities
class SnapshotHistory(Generic[T]):
def __init__(self, max_size: int = 50) -> None:
self._snapshots: list[ConfigMemento[T]] = []
self._max_size = max_size
def save(self, memento: ConfigMemento[T]) -> None:
self._snapshots.append(memento)
if len(self._snapshots) > self._max_size:
self._snapshots.pop(0)
def get_by_id(self, snapshot_id: str) -> ConfigMemento[T] | None:
return next(
(s for s in self._snapshots if s.id == snapshot_id), None
)
def get_by_tag(self, tag: str) -> list[ConfigMemento[T]]:
return [s for s in self._snapshots if tag in s.tags]
def latest(self) -> ConfigMemento[T] | None:
return self._snapshots[-1] if self._snapshots else None
def list_all(self) -> list[ConfigSnapshot[T]]:
return [s.get_snapshot() for s in self._snapshots]
# [step] Implement the configurable originator
@dataclass
class AppConfig:
theme: str = "light"
language: str = "en"
features: dict[str, bool] = field(default_factory=dict)
rate_limits: dict[str, int] = field(
default_factory=lambda: {"requests_per_minute": 60, "burst_size": 10}
)
class AppConfigManager:
def __init__(self, initial: AppConfig) -> None:
self._config = copy.deepcopy(initial)
self._history: SnapshotHistory[AppConfig] = SnapshotHistory()
def update(
self,
label: str,
tags: list[str] | None = None,
**changes: Any,
) -> None:
memento = ConfigMemento(self._config, label, tags)
self._history.save(memento)
for key, value in changes.items():
if hasattr(self._config, key):
setattr(self._config, key, value)
def rollback(
self, snapshot_id: str | None = None
) -> ConfigSnapshot[AppConfig] | None:
memento = (
self._history.get_by_id(snapshot_id)
if snapshot_id
else self._history.latest()
)
if memento is None:
return None
snapshot = memento.get_snapshot()
self._config = copy.deepcopy(snapshot.state)
return snapshot
@property
def config(self) -> AppConfig:
return self._config
def get_history(self) -> list[ConfigSnapshot[AppConfig]]:
return self._history.list_all()Memento Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Memento Pattern in the Real World
“Think of a video game save point. When you save, the game (originator) packages your character's stats, inventory, and position into a save file (memento). The save system (caretaker) stores these files without understanding their contents. When you die and reload, the save file is handed back to the game, which restores everything exactly as it was.”