Updating NamedTuple members based on a function and previous value 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:

&gt;&gt;&gt; sett = GameSettings(fps=30, is_running=True)

&gt;&gt;&gt; sett.update(lambda x: not x, "is_running")
GameSettings(fps=30, is_running=False)

&gt;&gt;&gt; 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) -&gt; GameSettings:
        return reduce(lambda acc, k: acc._replace(**{k: f(getattr(acc, k))}), keys, self)

    def update2(self, f: Callable[[T], T], **kwargs) -&gt; GameSettings:
        return self._replace(**{k: f(getattr(self, k)) for k, _ in kwargs.items()})
I decided that since I'll likely never need to associate the same function with multiple keys, I might as well just associate the keys with a function directly:

def update3(self, **kwargs: Callable[[T], T]) -&gt; GameSettings:
    return self._replace(**{k: f(getattr(self, k)) for k, f in kwargs.items()})

&gt;&gt;&gt; sett = GameSettings(fps=30, is_running=True)

&gt;&gt;&gt; sett.update3(is_running=lambda x: not x)
GameSettings(fps=30, is_running=False)


The code still isn't beautiful, but it makes a lot more sense when calling now.