How to Use Command Patterns in Python

by SkillAiNest

Have you ever used the undo button in an app or scheduled tasks to run later? They both rely on the same idea: Converting actions into objects.

This is the command pattern. Instead of calling a method directly, you package the call — the action, its target, and any arguments — into an object. This object can be stored, passed around, executed later, or canceled.

In this tutorial, you’ll learn what the command pattern is and how to implement it in Python with a practical text editor example that supports undo.

You can find the code for this tutorial. on GitHub.

Conditions

Before we begin, make sure you have:

  • Python 3.10 or higher is installed.

  • Basic understanding of Python classes and methods

  • Familiarity with Object Oriented Programming (OOP) concepts

Let’s begin!

Table of Contents

What is a command pattern?

gave Command pattern A behavior is a design pattern that encapsulates a request as an object. It allows you to:

  • Parameterize. Callers with different actions

  • queue or schedule Operations for later execution

  • Support undo/redo Maintaining the history of executed orders

The pattern has four main participants:

  • Order: A one-to-one interface execute() method (and optionally undo())

  • Concrete command: Instruments execute() And undo() For a specific action

  • The receiver: something that actually works (eg, a document)

  • inviter: Triggers commands and manages history.

Think of a restaurant. The customer (client) tells the waiter what they want. The waiter writes it on the ticket (command) and hands it to the kitchen (receiver). The waiter doesn’t make the food – they just manage the tickets. If you change your mind, the waiter can cancel the ticket before it reaches the kitchen.

Setting up the recipient

We will create a simple document editor. gave The receiver Here is Document The class knows how to insert and delete text, but it has no idea who is calling it or why.

class Document:
    def __init__(self):
        self.content = ""

    def insert(self, text: str, position: int) -> None:
        self.content = (
            self.content(:position) + text + self.content(position:)
        )

    def delete(self, position: int, length: int) -> None:
        self.content = (
            self.content(:position) + self.content(position + length:)
        )

    def show(self) -> None:
        print(f'Document: "{self.content}"')

insert Places the text at the given position. delete Removes length characters from a given position. Both are simple methods with no history or prescriptive awareness. And this is intentional.

Defining commands

Now let’s define a premise. Command Interface using abstract class:

from abc import ABC, abstractmethod

class Command(ABC):
    @abstractmethod
    def execute(self) -> None:
        pass

    @abstractmethod
    def undo(self) -> None:
        pass

Any concrete command must implement both. execute And undo. This makes a complete history possible.

InsertCommand

InsertCommand Stores text and position at creation time:

class InsertCommand(Command):
    def __init__(self, document: Document, text: str, position: int):
        self.document = document
        self.text = text
        self.position = position

    def execute(self) -> None:
        self.document.insert(self.text, self.position)

    def undo(self) -> None:
        self.document.delete(self.position, len(self.text))

when execute() It is called, it inserts text. when undo() That said, it deletes exactly what was inserted. Pay attention to it. undo is the reverse of execute – This is a key design requirement.

DeleteCommand

Now let’s code. DeleteCommand:

class DeleteCommand(Command):
    def __init__(self, document: Document, position: int, length: int):
        self.document = document
        self.position = position
        self.length = length
        self._deleted_text = ""  # stored on execute, used on undo

    def execute(self) -> None:
        self._deleted_text = self.document.content(
            self.position : self.position + self.length
        )
        self.document.delete(self.position, self.length)

    def undo(self) -> None:
        self.document.insert(self._deleted_text, self.position)

DeleteCommand There is one important detail: it captures deleted text. during execute()not at creation time. This is because we don’t know what the text at that position is until the command is actually run. without it, undo() Don’t know what to restore.

Invoker: Running and undoing commands

gave the caller is the object that executes commands and holds the history stack. He has no idea what a document is or how text editing works. It only manages command objects.

class EditorInvoker:
    def __init__(self):
        self._history: list(Command) = ()

    def run(self, command: Command) -> None:
        command.execute()
        self._history.append(command)

    def undo(self) -> None:
        if not self._history:
            print("Nothing to undo.")
            return
        command = self._history.pop()
        command.undo()
        print("Undo successful.")

run() Executes the command and pushes it onto the history stack. undo() Pops the last command and calls it. undo() The method stack naturally gives you the correct order: last in, null first.

Putting it all together

Let’s put it all together and go through a real editing session:

doc = Document()
editor = EditorInvoker()

# Type a title
editor.run(InsertCommand(doc, "Quarterly Report", 0))
doc.show()

# Add a subtitle
editor.run(InsertCommand(doc, " - Finance", 16))
doc.show()

# Oops, wrong subtitle — undo it
editor.undo()
doc.show()

# Delete "Quarterly" and replace with "Annual"
editor.run(DeleteCommand(doc, 0, 9))
doc.show()

editor.run(InsertCommand(doc, "Annual", 0))
doc.show()

# Undo the insert
editor.undo()
doc.show()

# Undo the delete (restores "Quarterly")
editor.undo()
doc.show()

These results:

Document: "Quarterly Report"
Document: "Quarterly Report - Finance"
Undo successful.
Document: "Quarterly Report"
Document: " Report"
Document: "Annual Report"
Undo successful.
Document: " Report"
Undo successful.
Document: "Quarterly Report"

Here’s a step-by-step breakdown of how (and why) it works:

  • Every single one InsertCommand And DeleteCommand Has its own instructions for both doing and undoing.

  • EditorInvoker Never appears inside a command. It just calls. execute() And undo().

  • document (DocumentNever thinks about history. It changes its contents when called.

Each participant has a single, clear responsibility.

Extensions with macros

One of the lesser-known benefits of the command pattern is that commands are just objects. So you can group them. Here is one MacroCommand which bundles several commands and cancels them as a unit:

class MacroCommand(Command):
    def __init__(self, commands: list(Command)):
        self.commands = commands

    def execute(self) -> None:
        for cmd in self.commands:
            cmd.execute()

    def undo(self) -> None:
        for cmd in reversed(self.commands):
            cmd.undo()

# Apply a heading format in one shot: clear content, insert formatted title
macro = MacroCommand((
    DeleteCommand(doc, 0, len(doc.content)),
    InsertCommand(doc, "== Annual Report ==", 0),
))

editor.run(macro)
doc.show()

editor.undo()
doc.show()

It gives the following output:

Document: "== Annual Report =="
Undo successful.
Document: "Quarterly Report"

A macro executes its commands in reverse order. This is true because the last thing that was done before must be undone.

When to use command patterns

The command pattern is a good fit when:

  • You need to undo/redo.: The pattern is practically made for this. Store executed commands in the stack and reverse them.

  • You need to queue or schedule operations.: Commands are objects, so you can queue them, serialize them, or delay execution.

  • You want to separate the caller from the action.: The requester does not need to know what the command does. It just drives it.

  • You need to support macros or batch operations.: Group commands into composites and run them together, as shown above.

Avoid it when:

  • Operations are simple and will never require undo or queuing. The pattern adds classes and indirection that a simple CRUD action might not be capable of.

  • The commands would need to share enough state to break the “encapsulate the request” idea.

The result

I hope you found this tutorial useful. To summarize, the command pattern turns actions into objects. And that single idea opens up a lot: undo/redo, queuing, macros, and a clean separation between what triggers an action and what the action does.

We built a document editor from scratch InsertCommand, DeleteCommandone EditorInvoker With a date stack, and a MacroCommand For batched editing. Each class knew exactly one thing and did it well.

As a next step, try extending the editor with a RedoCommand. You’ll need another stack with history to return undo commands.

Happy coding!

You may also like

Leave a Comment

At Skillainest, we believe the future belongs to those who embrace AI, upgrade their skills, and stay ahead of the curve.

Get latest news

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

@2025 Skillainest.Designed and Developed by Pro