[Tutor] Functional Programming in Python
Steven D'Aprano
steve at pearwood.info
Sun Apr 5 07:23:47 CEST 2015
On Sat, Apr 04, 2015 at 09:53:28PM -0400, WolfRage wrote:
> <SNIP>
> So I was surprised I did not get more feedback on my abused coroutine,
> maybe that is good or bad, not sure.
Perhaps people didn't understand it. Or see it :-)
> Any ways I am on to trying to make that coroutine act more like the
> State Pattern from Gang of Four. And well reading this:
> http://gameprogrammingpatterns.com/state.html
Now you're fighting the paradigm. Functional programming works best for
code that avoids side-effects. But changes of state are naturally a
side-effect! I was sitting, now I am walking. It's still me, but my
state is different.
Now of course you can emulate state changes using purely functional
code, but its harder and less efficient.
> I am not sure how to do this:
> class Heroine
> {
> public:
> virtual void handleInput(Input input)
> {
> state_->handleInput(*this, input);
> }
>
> virtual void update()
> {
> state_->update(*this);
> }
>
> // Other methods...
> private:
> HeroineState* state_;
> };
>
> (Pointing to the different classes. Since C++ has virtual methods but
> Python does not?) in Python? Do I just reference the new method? Because
> state_ will always be the correct object?
Hmmm, I'm not sure, but I think that we would say that *all* Python
methods are virtual in C++ terms. They are all resolved at runtime.
There are a few ways we might do this in Python.
class Heroine:
def __init__(self):
self.state = "standing" # Or use an Enum in Python 3.4
def jump(self):
if self.state == "standing":
self.state = "jumping"
...
elif self.state == "jumping":
pass
# Dispatch table of keys to actions.
KEYMAP = {'b': jump,
'd': duck,
'f': run,
...
}
def handle_keypress(self, key):
method = self.KEYMAP.get(key, None)
if method:
method(self)
We look up the key press, and get a reference to the method itself, not
the name of the method. (Or None.) We then call that method by providing
self as the first argument. Nice and easy. It's so easy that I'm not
sure if it's even worth discussing alternatives.
Another alternative might be to use delegation, or object composition.
Give the Heroine class all the methods which don't change. For the
methods which do change, create a separate FooState class for each
state:
class JumpingState:
def jump(self):
pass
def duck(self):
...
class RunningState:
...
class Heroine:
def __init__(self):
self.state = StandingState()
# Dispatch table of keys to method names.
KEYMAP = {'b': 'jump',
'd': 'duck',
'f': 'run',
...
}
def handle_keypress(self, key):
name = self.KEYMAP.get(key, None)
if name:
newstate = getattr(self.state, name)()
if newstate:
self.state = newstate()
Methods of the state object can signal a change of state by returning
the class representing the new state. Otherwise, they can return None to
signal no change of state.
Here is how we might do this using inheritence instead of composition.
Start with a base class and a bunch of default methods which
conditionally raise:
class BaseHeroine:
def jump(self):
if self.__class__ is BaseHeroine:
raise RuntimeError
def run(self):
if self.__class__ is BaseHeroine:
raise RuntimeError
def duck(self):
if self.__class__ is BaseHeroine:
raise RuntimeError
# Dispatch table of keys to method names.
KEYMAP = {'b': 'jump',
'd': 'duck',
'f': 'run',
...
}
def handle_keypress(self, key):
name = self.KEYMAP.get(key, None)
if name:
getattr(self.state, name)()
Now have a bunch of subclasses, one for each state. Override only the
methods you need. (Remember, the default behaviour for each method is to
do nothing, if called from a subclass. They only raise if called from
the base class.)
class JumpHeroine(BaseHeroine): ...
class StandHeroine(BaseHeroine): ...
class RunHeroine(BaseHeroine): ...
Now create an instance, and set its state:
heroine = BaseHeroine()
heroine.__class__ = StandHeroine
State transitions are managed by *changing the instance's class*.
Methods can do that themselves:
def spam(self):
self.__class__ = RunHeroine # Don't instantiate the class.
print("Spam spam spam loverlllly spam!!!!")
Now, I haven't tested any of the above code, but here is a short and
simple demonstration showing that it does work:
py> class X:
... def display(self):
... print("in X")
... def method(self):
... self.display()
... print(self, type(self), self.__class__)
...
py> class Y(X):
... def display(self):
... print("in Y")
...
py> instance = X()
py> instance.method()
in X
<__main__.X object at 0xb7a9d84c> <class '__main__.X'> <class '__main__.X'>
py> instance.__class__ = Y # dynamically change the class
py> instance.method()
in Y
<__main__.Y object at 0xb7a9d84c> <class '__main__.Y'> <class '__main__.Y'>
Cool, huh? :-)
Having written all this out, I'm now starting to lean towards the
inheritence version.
Now, how you would this in a purely functional style... I have no idea!
--
Steve
More information about the Tutor
mailing list