You would figure that the namesake of this website would have a post about it. Welcome to the Command Pattern. I first read about it in the famous Gang of Four book [Gamma et al]
Notes
This design pattern is useful to implement scripting and undo/redo behavior.
Updates:
- Latest Notebook: https://colab.research.google.com/drive/18icQ2U3Ln2HCV_O75uJtzFdN8zkHaoUu?usp=sharing
- Fixed undo bug
- Code solid, working on prose next
Prerequisites
- Basic Python knowledge
- classes
- functions
- file input/output
- great learning sites:
- https://www.executeprogram.com/courses/python-for-programmers
- todo add free one…
Explanations & Definitions
If I had to describe it to another programmer or technically adept person, I would say its just functions. Functions all the way down. But the special part is how you handle state and the ability to undo state changes. Or, script it!
Let us let the machine try and explain it:
Imagine you’re playing with a toy robot. Instead of controlling the robot directly, you have a special remote control. Each button on the remote is like a “command” that tells the robot what to do.
When you press a button, the remote doesn’t actually make the robot move. Instead, it sends a message to the robot saying “do this action”. The robot then follows that instruction.
This is kind of like how the Command pattern works in computer programs. Instead of one part of the program directly telling another part what to do, it creates a “command” that can be sent and followed later. This makes it easier to add new commands, undo actions, or even save a list of commands to do later.
– Claude3.5 Sonnet
Implementation Motivation
This is my implementation, there are many like this but this what I would consider an easy to teach and implement solution. You can always add features, ERRRR… complications, later 🙂
State or “World”
State is part of most developer’s lives, but really you can think about this as the data you care about in the program. It could be your air-line reservation, forum post, baby photos. You don’t want your data to be lost. State is your data or world, and often like the real-world the state is hierarchical.
Mutable World or Immutable World?
I’ll skip right past the mutable world. It is useful for large simulations, but that is another topic and I will leave this for another moment. Sometimes the memory overhead actually matters, because you cannot afford a machine with that much RAM. If you have limited hardware, consider a hybrid-mutable-immutable-world.
I consider Immutable World the cleanest approach and most ideal for scripting.

The Immutable World
new_world = get_next_state(world, command)
So it is an agreement that any time the “World” will be mutated, we make a copy and return new state. We never mutate state.
We will also keep all of our state objects in a stack linked to their commands that have been performed.
all_states = [] # list of all worlds ever
Function Interfaces
Finite State Machines (FSM) are very important for system design. In this scenario we will ensure to call our functions on our state/world objects to maintain consistent design. From this point on we will refer to world as state as it is the preferred term of the author.
It helps to have an actual problem to solve to teach the command pattern. We will start with the humble calculator.
Command Processor
# CRL LAB - COMMAND PATTERN - A
# COPYRIGHT 2024 CRYPTIDE RESEARCH - ALL RIGHTS RESERVED
# LICENSED UNDER GPL3
# generic processor that is slightly tuned for us to teach
class CommandProcessor:
def __init__(self, config):
self.config = config
self.history = [config.get_default_cmd()]
self._value = config.get_default_value()
def exec(self, op, a, b=None):
self._value, cmd_link = self.config.exec(op, a, b)
self.history.append((self._value, cmd_link))
return self._value
def clear(self):
self.history.clear()
self.config.clear()
def undo(self):
if len(self.history) == 0:
raise ValueError("No operations to undo")
undo_value, undo_cmd = self.history.pop()
new_value, _ = self.history[-1] #look at last one and get value
self._value = new_value
#this following approach is half working for side-effect based systems example
#new_value = self.config.undo(self.history[-1])
#but what else do we need?
return self._value
def value(self):
return self._value
Note that this processor implementation is just a starting point. The core ingredients. As we develop our application we may dream up new features of the interfaces.
You can note that there is no specific implementation baked into the design. It is meant to operate as a shim or a harness to run specific state/command patterns.
We have a configuration object passed in which contains the domain specific code we will be wrapping in the façade. A history list and helper value_history to aid in debugging.
This is the core function, this executes a command. a, b are arguments to the command. op is the operation to be performed.
History is preserved and we return the result of the execution is returned for easy of use of the API.

Calculator Commando
from enum import Enum
# simple calculator command processor
class Calculator:
def __init__(self):
self._value = 0
# for referencing the payload
class CALC_ROW(Enum):
OP = 0
A = 1
B = 2
RESULT = 3
# public interface
def exec(self, op, a, b=None):
print(f"executing {op} {a} {b}")
coms = self._get_command_map()
if op not in coms:
raise ValueError(f"Invalid operation: {op}")
self._value = coms[op](a, b)
return (self._value, (op, a, b, self._value))
def value(self):
return self._value
def undo(self, tuple4):
op, b, a, _ = tuple4 # pull arguments in reverse order!!!
if a is None:
a = self._value
self._value = self._get_undo_map(op)(a, b)
return (self._value, (op, a, b, self._value))
def reset(self):
self._value = self.get_default_value()
def get_default_value(self):
return 0
def get_default_cmd(self):
return (self.get_default_value(), ('+', 0, 0, 0))
def clear(self):
self._value = 0
# private implementation
def _add(self, a, b):
if b is None:
b = self._value
return a + b
def _multiply(self, a, b):
if b is None:
b = self._value
return a * b
def _subtract(self, a, b):
if b is None:
b = self._value
return a - b
def _divide(self, a, b):
if b is None:
b = self._value
if b == 0:
raise ValueError("Division by zero is not allowed")
return a / b
def _get_command_map(self):
return {
'+': self._add,
'*': self._multiply,
'-': self._subtract,
'/': self._divide,
}
def _get_undo_map(self, op:str):
switch = {
'+': self._subtract,
'*': self._divide,
'-': self._add,
'/': self._multiply
}
return switch[op]
This is the domain specific part of the code. It would change depending on the task that the application programmer might need. Example usage of the latest code:
base_calc = Calculator()
x0 = base_calc.exec('+', 1, 2)
print('using implementation itself')
print(x0) # (3, ('+', 1, 2, 3))
print(base_calc.value()) # 3
x1 = base_calc.exec('+', 1)
print(x1) # (4, ('+', 1, None, 4))
print(base_calc.value()) # 4
At this point you might be wondering why we’ve done all this boiler plate. The final details:
# you can see the (value, (op, a, b, value))
# data structure here as an artifact to help with undo
# clear up and do the real Command Pattern
base_calc.clear()
print(base_calc.value())
print('wrap it in command processor')
calc = CommandProcessor(base_calc)
calc.exec('-', 10, 4)
print(calc.history[-1])
print(calc.value())
calc.exec('+', 1)
print(calc.history[-1])
print(calc.value())
print('undo last calc...')
calc.undo()
print(calc.value())
print('undo last calc...')
calc.undo()
print(calc.value())
0
wrap it in command processor
executing - 10 4
(6, ('-', 10, 4, 6))
6
executing + 1 None
(7, ('+', 1, None, 7))
7
undo last calc...
6
undo last calc...
0
Discussion
With the encoding of the inverse mapping of the operations between add/subtract and divide/multiply we can safely undo any operation but applying it in reverse. Not all commando’s will have this luxury. Think if there is a function that creates a file on disk. The undo of that command should do what?
DELETE THE FILE
Ok. we can end here for this section for the lab.
There will be an on-going discussion, but part 2 of this lab will transition to image manipulation and scripting support.
Thanks for reading!

