Search code examples
pythonoopfsm

Key Error: Python Finite State Machine?


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

Solution

  • 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.