I'm using a NamedTuple
to represent the state of my game while it's running. The user can alter these settings using key presses, so in my PyGame key handler, I'm writing things like:
if key_code == 273: # Up arrow
return game_settings._replace(fps=game_settings.fps + 5)
Each key press transforms the settings state by using the old value and _replace
.
I decided to try and emulate Clojure's update
function as an exercise, and because it may actually make more code more readable. It takes a function and a key, and updates the value at that key using the function. Example usage:
>>> sett = GameSettings(fps=30, is_running=True)
>>> sett.update(lambda x: not x, "is_running")
GameSettings(fps=30, is_running=False)
>>> sett.update2(lambda x: not x, is_running=None)
GameSettings(fps=30, is_running=False)
I created two different versions that each behave similarly but are slightly different. Version one accepts a string key. Version two accepts kwargs with a dummy value. I'm not super happy with either though. Having to pass a String to Version one fells messy any error prone. If I typo the String, I'll get an AttributeError
at runtime. Version two accepting a dummy value also feels off and is also error prone; although I do think it reads nicer without the quotes. The code for each is also quite ugly and dense.
The methods don't actually need to accept more than one key (I'll probably never pass more than one at a time), but I figured it barely changes the algorithm and it mirrors _replace
, so I decided why not.
I'd appreciate any thoughts here on how to set this up better or make the code cleaner.
from __future__ import annotations
from typing import NamedTuple, Callable, TypeVar
from functools import reduce
T = TypeVar("T")
class GameSettings(NamedTuple):
fps: int
is_running: bool = True
def update(self, f: Callable[[T], T], *keys) -> GameSettings:
return reduce(lambda acc, k: acc._replace(**{k: f(getattr(acc, k))}), keys, self)
def update2(self, f: Callable[[T], T], **kwargs) -> GameSettings:
return self._replace(**{k: f(getattr(self, k)) for k, _ in kwargs.items()})
NamedTuple
and alambda
) is a good idea at all. I wish you had shown the rest of the key handler function. \$\endgroup\$