Andy
Andy

Reputation: 33

Calling methods with class instance as argument in Python

Assuming I have the following code

IDLE = 0
STARTED = 1
STOPPED = 2
ERRORED = 3
# additional states as needed

class StateMachine:
    def __init__(self)
        self.state = IDLE

    def start(self):
        self.state = STARTED
        # do something

    def stop(self):
        self.state = STOPPED
        # do something

    def reset(self):
        self.state = IDLE
        # do something

Our current interface allows a client to change the state of an instance by stating the desired target state, at which point we run certain validation checks and then the appropriate method. I would ideally like to keep a dictionary mapping of the desired target state to the correct method to avoid massive and pointless if-statement blocks. i.e.

if target_state = STARTED:
    instance.start()
elif target_state = STOPPED:
    instance.stop()
...

But I'm uncertain as to whether or not the following solution is considered good practice or not (it feels a bit whacky calling methods from the class using an instance as arg).

state_mapping = {
    IDLE: StateMachine.reset,
    STARTED: StateMachine.start,
    ....
}

And then calling using:

action = state_mapping[target_state]
action(instance)
....

Any thoughts?

Upvotes: 3

Views: 105

Answers (2)

kdopen
kdopen

Reputation: 8215

One other alternative.

As your class is called "StateMachine", perhaps it should have a method to execute the state change?

In which case, you can use bound methods in your map

class StateMachine:
    ...

    def ChangeState(self, target):
        state_mapping = { IDLE: self.reset, STARTED: self.start, ... }
        state_mapping[target]()

You may want to deal with invalid target states, or just let it raise a KEY_ERROR exception.

Upvotes: 1

Moses Koledoye
Moses Koledoye

Reputation: 78554

Not so whacky.

However, the only thing one has to bear in mind is that action is an unbound method, which may not be very obvious at a first glance of the method call; except I know first-hand how that dictionary is defined.

I think a more readable alternative is to call the method from the instance:

state_mapping = {
    IDLE: "reset",
    STARTED: "start",
    ....
}

action = state_mapping[target_state]
getattr(instance, action)()

This will equally improve readability in the case when the method takes more than one argument.

Upvotes: 2

Related Questions