Here is my code for a simple implementation of a Finite State Machine in Python. I have run it multiple times to no avail. When the individual state classes do inherit from the individual class, they cannot access the food and sleep variables needed to run the logic of the machine. I believe I am getting a KeyError because every time I add a new state, it resets the Current State Value to None. How might I get around this error? Is there a better/proper way to structure my classes? I am intermediate at python, however OOP concepts have been particularly challenging for me. Any help and advice would be greatly appreciated!
class FSM():
def __init__(self):
self.states = {}
self.c_state = None
def update(self):
self.states[self.c_state].execute()
def addState(self, name, handler):
self.states[name] = handler
def startState(self, state):
self.c_state = state
def changeState(self, newstate):
print('The current state is:', self.c_state)
self.states[self.c_state].exit()
self.c_state = self.states[newstate]
self.states[self.c_state].enter()
class individual():
def __init__(self, name, food, sleep):
self.name = name
self.food = food
self.sleep = sleep
self.fsm = FSM()
class wander(individual):
def enter(self):
print('Entering Wander State')
def execute(self):
print('WANDERING')
if self.sleep == 0:
self.fsm.changeState('SLEEP')
elif 25 > self.sleep > 0 and self.food > 25:
self.fsm.changeState('WANDER')
self.sleep -= 5
self.food -= 5
elif self.food < 25:
self.fsm.changeState('EAT')
def exit(self):
print('Leaving Wander State')
class eat(individual):
def enter(self):
print('Entering Eating State')
def execute(self):
print('EATING')
if self.sleep > 0 and self.food < 25:
self.fsm.changeState('EAT')
self.food += 5
self.sleep -= 5
elif self.food > 25 and self.sleep > 0:
self.fsm.changeState('WANDER')
elif self.sleep == 0:
self.fsm.changeState('SLEEP')
def exit(self):
print('Exiting Eating State')
class sleep(individual):
def enter(self):
print('Entering Sleeping State')
def execute(self):
print('SLEEPING')
if 50 > self.sleep > 0:
self.fsm.changeState('SLEEP')
self.sleep += 5
elif self.sleep == 50 and self.food < 25:
self.fsm.changeState('EAT')
elif self.sleep == 50 and self.food > 25:
self.fsm.changeState('WANDER')
def exit(self):
print('Exiting Sleeping State')
aaron = individual('aaron', 10, 30)
aaron.fsm.addState('WANDER', wander(aaron.name, aaron.food, aaron.sleep))
aaron.fsm.addState('EAT', eat(aaron.name, aaron.food, aaron.sleep))
aaron.fsm.addState('SLEEP', sleep(aaron.name, aaron.food, aaron.sleep))
aaron.fsm.startState('WANDER')
print(aaron.fsm.c_state)
aaron.fsm.update()
File "C:/Users/Aaron/PycharmProjects/InDepthFSM/FSM.py", line 12, in update
self.states[self.c_state].execute()
File "C:/Users/Aaron/PycharmProjects/InDepthFSM/FSM.py", line 50, in execute
self.fsm.changeState('EAT')
File "C:/Users/Aaron/PycharmProjects/InDepthFSM/FSM.py", line 24, in changeState
self.states[self.c_state].exit()
KeyError: None
The issue here is that you're referring to different fsm objects when you perform your updates.
In the execute
code snippet, when you update state, the issue is that while the FSM
object exists for the individual object, it doesn't exist for the eat
class, and you try to update the state of the eat
class.
# Here, since self refers to the `eat` class, you are creating a new fsm object
# This means that you are in fact referring to an empty set of states.
elif self.sleep == 0:
self.fsm.changeState('SLEEP')
Inheritance doesn't mean that the classes share a single FSM
instance, so if you want to operate on the FSM
object belonging to the individual class you need to pass the individual as an input.
That said, the eat
piece here really shouldn't be a class, but should be a function of individual
, rather than inheriting from it. For instance:
class individual(object):
def __init__(self, ...):
# make object
def eat(self):
print('EATING')
if self.sleep > 0 and self.food < 25:
self.fsm.changeState('EAT')
self.food += 5
self.sleep -= 5
elif self.food > 25 and self.sleep > 0:
self.fsm.changeState('WANDER')
elif self.sleep == 0:
self.fsm.changeState('SLEEP')
in this case, self
actually refers to the individual
object.
I think you've got some confusions about OOP in general. Inheritance doesn't interact at all with specific object instances. Instead, it's like saying: "I need to make a new object, but let me use this old one as a template and just add to it".
What you're trying to do here is much closer to creating a singleton, which is one object that has a single state that is referred to elsewhere. Not everything is supposed to be an object. For instance, handlers are typically functions, and relying on a magic execute
function rather than just executing a function is pretty circuitous.