How to Implement the Command Pattern with Undo in Python
Python code demonstrating the Command design pattern with undo and redo support using action objects and a history manager.
Python code
72 linesclass Command:
def execute(self):
raise NotImplementedError
def undo(self):
raise NotImplementedError
class AddTextCommand(Command):
def __init__(self, document, text):
self.document = document
self.text = text
def execute(self):
self.document.append(self.text)
def undo(self):
self.document.pop()
class RemoveLastCommand(Command):
def __init__(self, document):
self.document = document
self.removed_text = None
def execute(self):
self.removed_text = self.document.pop()
def undo(self):
self.document.append(self.removed_text)
class CommandHistory:
def __init__(self):
self.history = []
self.redo_stack = []
def execute(self, command):
command.execute()
self.history.append(command)
self.redo_stack.clear()
def undo(self):
if self.history:
command = self.history.pop()
command.undo()
self.redo_stack.append(command)
def redo(self):
if self.redo_stack:
command = self.redo_stack.pop()
command.execute()
self.history.append(command)
if __name__ == "__main__":
doc = []
history = CommandHistory()
history.execute(AddTextCommand(doc, "Hello"))
history.execute(AddTextCommand(doc, "World"))
history.execute(RemoveLastCommand(doc))
print("After commands:", doc)
history.undo()
print("After one undo:", doc)
history.undo()
print("After two undos:", doc)
history.redo()
print("After redo:", doc)
Output
After commands: ['Hello']
After one undo: ['Hello', 'World']
After two undos: ['Hello']
After redo: ['Hello', 'World']
How it works
This code defines a Command base class that every action implements with execute() and undo(). Each command encapsulates the state needed to reverse itself, like the text to add or the removed item. The CommandHistory class records executed commands and uses a stack to support undo and a redo stack to allow reapplying undone actions. When a new command is executed, the redo stack is cleared to maintain a linear history. By treating every action as an object, the pattern decouples the UI or caller from the actual operations, making it easy to add undo/redo, logging, or transactional behavior.
Common mistakes
- Forgetting to set the redo stack for each command, so undo/redo state leaks between commands.
- Not storing the exact state needed to reverse an operation, such as the removed text.
- Mutating shared state directly inside commands without documenting assumptions, causing unpredictable undo behavior.
Variations
- Use a list of tuples to store commands and their inverse actions instead of separate classes.
- Implement a single function that returns an undo function, using closures to capture state.
Real-world use cases
- Text editors and IDEs use this pattern to implement undo/redo for edits like typing or deleting selections.
- Database transaction managers record operations as command objects to support rollback and replay.
- GUI applications (e.g., drawing tools) use command objects to allow users to undo brush strokes or layer changes.
Sponsored
More from OOP & classes
- Add property getter setter validation in Python easy
- Binary Tree Inorder Traversal in Python easy
- Borg pattern shared state in Python medium
- Bridge Pattern in Python: Separate Abstraction from Implementation medium
- Composable Predicates with the &, |, ~ Operators in Python medium
- Composition over Inheritance: How to Build a Wallet Account in Python easy
Keep learning
Related tutorials and quizzes for this topic.