Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Command и Undo/Redo
command

Command и Undo/Redo

Инкапсуляция действий как объектов. Реализация отмены/повтора операций. Команды через callable-объекты и functools.partial.

Command и Undo/Redo

Команда превращает действие в объект, который можно сохранить, отменить и повторить.


#Command: инкапсуляция действия

Command — поведенческий паттерн, инкапсулирующий запрос как объект, позволяя параметризовать клиентов с разными запросами и поддерживать undo/redo.

#Проблема: размазанная логика выполнения

# ❌ ПЛОХО: Логика выполнения размазана class TextEditor: def insert_text(self, text: str): print(f"Inserting: {text}") def delete_text(self, start: int, end: int): print(f"Deleting {start}:{end}") # Клиент знает о всех методах editor = TextEditor() editor.insert_text("Hello") editor.delete_text(0, 5) # Невозможно отменить, сохранить, поставить в очередь

#Решение 1: Command через класс

# ✅ ХОРОШО: Каждое действие — объект from abc import ABC, abstractmethod class Command(ABC): @abstractmethod def execute(self): pass @abstractmethod def undo(self): pass class TextEditor: def __init__(self): self._text = "" def insert(self, text: str, position: int): self._text = self._text[:position] + text + self._text[position:] def delete(self, start: int, end: int): self._text = self._text[:start] + self._text[end:] @property def text(self) -> str: return self._text class InsertCommand(Command): def __init__(self, editor: TextEditor, text: str, position: int): self._editor = editor self._text = text self._position = position self._executed = False def execute(self): self._editor.insert(self._text, self._position) self._executed = True def undo(self): if self._executed: self._editor.delete(self._position, self._position + len(self._text)) self._executed = False class DeleteCommand(Command): def __init__(self, editor: TextEditor, start: int, end: int): self._editor = editor self._start = start self._end = end self._deleted_text = None def execute(self): self._deleted_text = self._editor.text[self._start:self._end] self._editor.delete(self._start, self._end) def undo(self): if self._deleted_text: self._editor.insert(self._deleted_text, self._start) self._deleted_text = None # Использование editor = TextEditor() history = [] # Выполнение команд cmd1 = InsertCommand(editor, "Hello", 0) cmd1.execute() history.append(cmd1) cmd2 = InsertCommand(editor, " World", 5) cmd2.execute() history.append(cmd2) print(editor.text) # Hello World # Undo history.pop().undo() # Undo: InsertCommand print(editor.text) # Hello history.pop().undo() # Undo: InsertCommand print(editor.text) # ""

#Решение 2: Command через callable

# ✅ Проще: функции как команды from typing import Callable, List class CommandHistory: def __init__(self): self._history: List[Callable] = [] self._undo_stack: List[Callable] = [] def execute(self, command: Callable, undo: Callable = None): command() self._history.append(command) if undo: self._undo_stack.append(undo) self._redo_stack = [] # Сброс redo при новом действии def undo(self): if self._undo_stack: undo_fn = self._undo_stack.pop() undo_fn() # Использование editor = TextEditor() history = CommandHistory() def insert(): editor.insert("X", 0) def undo_insert(): editor.delete(0, 1) history.execute(insert, undo_insert) history.undo()

#Решение 3: Command через functools.partial

# ✅ Pythonic: partial для команд from functools import partial from typing import Protocol class Command(Protocol): def __call__(self): ... def undo(self): ... class TextEditor: def __init__(self): self._text = "" self._history: List[Command] = [] def insert(self, text: str, position: int): self._text = self._text[:position] + text + self._text[position:] def delete(self, start: int, end: int): self._text = self._text[:start] + self._text[end:] @property def text(self) -> str: return self._text def execute(self, command: Command): command() self._history.append(command) def undo(self): if self._history: self._history.pop().undo() class InsertCommand: def __init__(self, editor: TextEditor, text: str, position: int): self.editor = editor self.text = text self.position = position def __call__(self): self.editor.insert(self.text, self.position) def undo(self): self.editor.delete(self.position, self.position + len(self.text)) # Использование editor = TextEditor() editor.execute(InsertCommand(editor, "Hello", 0)) editor.execute(InsertCommand(editor, " World", 5)) print(editor.text) # Hello World editor.undo() print(editor.text) # Hello

#Undo/Redo: история команд

#Стек undo/redo

from typing import List, Optional class CommandHistory: def __init__(self): self._undo_stack: List[Command] = [] self._redo_stack: List[Command] = [] def execute(self, command: Command): command.execute() self._undo_stack.append(command) self._redo_stack.clear() # Сброс redo при новом действии def undo(self) -> bool: if not self._undo_stack: return False command = self._undo_stack.pop() command.undo() self._redo_stack.append(command) return True def redo(self) -> bool: if not self._redo_stack: return False command = self._redo_stack.pop() command.execute() self._undo_stack.append(command) return True @property def can_undo(self) -> bool: return len(self._undo_stack) > 0 @property def can_redo(self) -> bool: return len(self._redo_stack) > 0 # Использование history = CommandHistory() history.execute(InsertCommand(editor, "A", 0)) history.execute(InsertCommand(editor, "B", 1)) history.execute(InsertCommand(editor, "C", 2)) # editor.text = "ABC" history.undo() # "AB" history.undo() # "A" history.redo() # "AB" history.redo() # "ABC"

#Практическое применение

#Макросы (последовательность команд)

class MacroCommand(Command): """Команда из последовательности команд""" def __init__(self, commands: List[Command] = None): self._commands = commands or [] def add(self, command: Command): self._commands.append(command) def execute(self): for cmd in self._commands: cmd.execute() def undo(self): # Отменяем в обратном порядке for cmd in reversed(self._commands): cmd.undo() # Использование macro = MacroCommand() macro.add(InsertCommand(editor, "Hello", 0)) macro.add(InsertCommand(editor, " ", 5)) macro.add(InsertCommand(editor, "World", 6)) history.execute(macro) # editor.text = "Hello World" history.undo() # editor.text = ""

#Транзакции

from contextlib import contextmanager class Transaction: """Транзакция с откатом""" def __init__(self, history: CommandHistory): self._history = history self._commands: List[Command] = [] def execute(self, command: Command): command.execute() self._commands.append(command) def commit(self): for cmd in self._commands: self._history._undo_stack.append(cmd) self._history._redo_stack.clear() def rollback(self): for cmd in reversed(self._commands): cmd.undo() @contextmanager def transaction(history: CommandHistory): tx = Transaction(history) try: yield tx tx.commit() except Exception: tx.rollback() raise # Использование with transaction(history) as tx: tx.execute(InsertCommand(editor, "A", 0)) tx.execute(InsertCommand(editor, "B", 1)) # При ошибке — автоматический rollback

#Очереди команд

import queue import threading class CommandQueue: def __init__(self): self._queue = queue.Queue() self._worker = threading.Thread(target=self._process, daemon=True) self._worker.start() def enqueue(self, command: Command): self._queue.put(command) def _process(self): while True: command = self._queue.get() command.execute() self._queue.task_done() # Использование cmd_queue = CommandQueue() cmd_queue.enqueue(InsertCommand(editor, "A", 0)) cmd_queue.enqueue(InsertCommand(editor, "B", 1)) # Команды выполняются в фоновом потоке

#Command для GUI

from typing import Callable class Button: def __init__(self, label: str, command: Callable = None): self.label = label self.command = command def click(self): if self.command: self.command() class CopyCommand(Command): def __init__(self, text_editor: TextEditor): self._editor = text_editor self._clipboard = "" def execute(self): self._clipboard = self._editor.text print(f"Copied: {self._clipboard}") def undo(self): self._clipboard = "" print("Clipboard cleared") class PasteCommand(Command): def __init__(self, text_editor: TextEditor, clipboard: str): self._editor = text_editor self._clipboard = clipboard def execute(self): self._editor.insert(self._clipboard, len(self._editor.text)) def undo(self): self._editor.delete(len(self._editor.text) - len(self._clipboard), len(self._editor.text)) # Использование editor = TextEditor() copy_btn = Button("Copy", CopyCommand(editor).execute) paste_btn = Button("Paste", PasteCommand(editor, "text").execute) copy_btn.click() # Copied: ...

#Command для задач

from abc import ABC, abstractmethod from datetime import datetime from typing import Optional class Task(ABC): @abstractmethod def execute(self): pass class SendEmailTask(Task): def __init__(self, to: str, subject: str, body: str): self.to = to self.subject = subject self.body = body self.executed_at: Optional[datetime] = None def execute(self): print(f"Sending email to {self.to}: {self.subject}") self.executed_at = datetime.now() class TaskQueue: def __init__(self): self._tasks: List[Task] = [] def add(self, task: Task): self._tasks.append(task) def execute_all(self): for task in self._tasks: task.execute() self._tasks.clear() # Использование queue = TaskQueue() queue.add(SendEmailTask("user@example.com", "Welcome", "Hello!")) queue.add(SendEmailTask("admin@example.com", "New user", "Registered")) queue.execute_all()

#Что дальше?

Изучите тему Iterator и Generator patterns для работы с последовательностями.

Далее: Iterator и Generator patterns