Сохранение состояния объектов. Pickle для сериализации. dataclasses.asdict и __getstate__/__setstate__.
Memento сохраняет момент времени. Pickle сохраняет объекты.
Memento — поведенческий паттерн, позволяющий сохранять и восстанавливать состояние объекта, не нарушая инкапсуляцию.
# ❌ ПЛОХО: Нарушение инкапсуляции
class Editor:
def __init__(self):
self.text = ""
self.cursor = 0
# Клиент знает о внутренностях
editor = Editor()
editor.text = "Hello"
editor.cursor = 5
# Сохранение требует знания структуры
saved_state = (editor.text, editor.cursor)
# Восстановление
editor.text, editor.cursor = saved_state# ✅ ХОРОШО: Инкапсуляция сохранена
class Memento:
"""Хранитель состояния — неизменяемый"""
def __init__(self, text: str, cursor: int):
self._text = text
self._cursor = cursor
@property
def text(self) -> str:
return self._text
@property
def cursor(self) -> int:
return self._cursor
class Editor:
def __init__(self):
self._text = ""
self._cursor = 0
def type(self, text: str):
self._text = self._text[:self._cursor] + text + self._text[self._cursor:]
self._cursor += len(text)
@property
def text(self) -> str:
return self._text
def save(self) -> Memento:
"""Создаёт снимок состояния"""
return Memento(self._text, self._cursor)
def restore(self, memento: Memento):
"""Восстанавливает состояние"""
self._text = memento.text
self._cursor = memento.cursor
# Использование
editor = Editor()
editor.type("Hello")
editor.type(" World")
# Сохранение
saved = editor.save()
editor.type("!")
print(editor.text) # Hello World!
# Восстановление
editor.restore(saved)
print(editor.text) # Hello World# ✅ Проще: Memento как словарь
from typing import Dict, Any
class Editor:
def __init__(self):
self._state: Dict[str, Any] = {"text": "", "cursor": 0}
def type(self, text: str):
cursor = self._state["cursor"]
self._state["text"] = (
self._state["text"][:cursor] + text +
self._state["text"][cursor:]
)
self._state["cursor"] = cursor + len(text)
def save(self) -> Dict[str, Any]:
return self._state.copy()
def restore(self, state: Dict[str, Any]):
self._state = state.copy()
# Использование
editor = Editor()
editor.type("Hello")
saved = editor.save()
editor.type(" World")
editor.restore(saved)# ✅ История снимков
from typing import List
class EditorWithHistory:
def __init__(self):
self._text = ""
self._cursor = 0
self._history: List[Memento] = []
self._redo_stack: List[Memento] = []
def type(self, text: str):
self.save() # Сохраняем перед изменением
cursor = self._cursor
self._text = self._text[:cursor] + text + self._text[cursor:]
self._cursor = cursor + len(text)
self._redo_stack.clear() # Сброс redo
def save(self):
self._history.append(Memento(self._text, self._cursor))
def undo(self):
if len(self._history) > 1:
self._redo_stack.append(self._history.pop())
self.restore(self._history[-1])
def redo(self):
if self._redo_stack:
self.save()
self.restore(self._redo_stack.pop())
@property
def text(self) -> str:
return self._text
def restore(self, memento: Memento):
self._text = memento.text
self._cursor = memento.cursor
# Использование
editor = EditorWithHistory()
editor.type("Hello")
editor.type(" World")
editor.type("!")
editor.undo() # "Hello World"
editor.undo() # "Hello"
editor.redo() # "Hello World"# ✅ Pickle для сохранения объектов
import pickle
class GameState:
def __init__(self):
self.player_name = "Hero"
self.level = 1
self.health = 100
self.inventory = ["sword", "shield"]
# Сохранение
state = GameState()
with open("save.pkl", "wb") as f:
pickle.dump(state, f)
# Загрузка
with open("save.pkl", "rb") as f:
loaded = pickle.load(f)
print(loaded.player_name) # Hero# ✅ Кастомная сериализация
import pickle
class DatabaseConnection:
def __init__(self, url: str):
self.url = url
self._connection = None # Не сериализуется!
self._connect()
def _connect(self):
self._connection = f"Connection({self.url})"
def __getstate__(self):
"""Вызывается при сериализации"""
state = self.__dict__.copy()
# Не сериализуем соединение
del state["_connection"]
return state
def __setstate__(self, state):
"""Вызывается при десериализации"""
self.__dict__.update(state)
# Восстанавливаем соединение
self._connect()
# Использование
db = DatabaseConnection("postgresql://localhost")
pickled = pickle.dumps(db)
restored = pickle.loads(pickled)
print(restored.url) # postgresql://localhost# ✅ dataclasses для снимков
from dataclasses import dataclass, asdict
from typing import Dict
@dataclass
class EditorState:
text: str = ""
cursor: int = 0
class Editor:
def __init__(self):
self._state = EditorState()
def type(self, text: str):
cursor = self._state.cursor
self._state.text = (
self._state.text[:cursor] + text +
self._state.text[cursor:]
)
self._state.cursor = cursor + len(text)
def save(self) -> Dict:
return asdict(self._state)
def restore(self, state: Dict):
self._state = EditorState(**state)
# Использование
editor = Editor()
editor.type("Hello")
saved = editor.save()
editor.type(" World")
editor.restore(saved)# ✅ Снимки состояния игры
import pickle
from datetime import datetime
@dataclass
class GameSnapshot:
timestamp: datetime
player_pos: tuple
player_health: int
enemies: list
inventory: dict
class Game:
def __init__(self):
self._snapshots = []
self.player_pos = (0, 0)
self.player_health = 100
self.enemies = []
self.inventory = {}
def save(self):
snapshot = GameSnapshot(
timestamp=datetime.now(),
player_pos=self.player_pos,
player_health=self.player_health,
enemies=self.enemies.copy(),
inventory=self.inventory.copy(),
)
self._snapshots.append(snapshot)
def load(self, index: int = -1):
snapshot = self._snapshots[index]
self.player_pos = snapshot.player_pos
self.player_health = snapshot.player_health
self.enemies = snapshot.enemies
self.inventory = snapshot.inventory
def save_to_file(self, filename: str):
with open(filename, "wb") as f:
pickle.dump(self._snapshots, f)
def load_from_file(self, filename: str):
with open(filename, "rb") as f:
self._snapshots = pickle.load(f)# ✅ Лог транзакций
from typing import List, Callable
import json
class TransactionLog:
def __init__(self):
self._log: List[dict] = []
def record(self, operation: str, data: dict):
self._log.append({
"operation": operation,
"data": data,
"timestamp": datetime.now().isoformat(),
})
def replay(self, handler: Callable[[str, dict], None]):
for entry in self._log:
handler(entry["operation"], entry["data"])
def save(self, filename: str):
with open(filename, "w") as f:
json.dump(self._log, f)
def load(self, filename: str):
with open(filename) as f:
self._log = json.load(f)
# Использование
log = TransactionLog()
log.record("credit", {"account": 123, "amount": 100})
log.record("debit", {"account": 123, "amount": 50})
def replay_handler(op: str, data: dict):
print(f"{op}: {data}")
log.replay(replay_handler)# ✅ copy.deepcopy как простой Memento
import copy
class Editor:
def __init__(self):
self.text = ""
self.cursor = 0
self.styles = {} # Вложенный объект
def save(self):
return copy.deepcopy(self.__dict__)
def restore(self, state):
self.__dict__.update(copy.deepcopy(state))
# deepcopy рекурсивно копирует всё, включая вложенные объекты| Паттерн | Когда использовать | Pythonic-реализация |
|---|---|---|
| Memento (класс) | Нужно скрыть состояние от клиента | Класс с приватными полями |
| Memento (dict) | Простое состояние | Словарь + copy() |
| Pickle | Сериализация сложных объектов | pickle.dump/load |
| dataclasses | Структурированное состояние | @dataclass + asdict |
Главный принцип: Memento сохраняет состояние, не нарушая инкапсуляцию.
Изучите тему Observer и Pub/Sub для реактивного программирования.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.