Kyle, you sounded so reasonable when you were trashing itertools.accumulate (which I now agree is horrible). But then you go and support Serhiy's madness: "smooth_signal = [average for average in  for x in signal for average in [(1-decay)*average + decay*x]]" which I agree is clever, but reads more like a riddle than readable code.
Anyway, I continue to stand by:
(y:= f(y, x) for x in iter_x from y=initial_y)
And, if that's not offensive enough, to its extension:
(z, y := f(z, x) -> y for x in iter_x from z=initial_z)
Why am I so obsessed? Because it will allow you to conveniently replace classes with more clean, concise, functional code. People who thought they never needed such a construct may suddenly start finding it indispensable once they get used to it.
How many times have you written something of the form?:
def __init__(self, initial_state, param_1, param_2):
self._param_2 = param_2
self._state = initial_state
def update_and_get_output(self, new_observation): # (or just __call__)
self._state = do_some_state_update(self._state, new_observation, self._param_1)
output = transform_state_to_output(self._state, self._param_2)
processor = StatefulThing(initial_state = initial_state, param_1 = 1, param_2 = 4)
processed_things = [processor.update_and_get_output(x) for x in x_gen]
I've done this many times. Video encoding, robot controllers, neural networks, any iterative machine learning algorithm, and probably lots of things I don't know about - they all tend to have this general form.
And how many times have I had issues like "Oh no now I want to change param_1 on the fly instead of just setting it on initialization, I guess I have to refactor all usages of this class to pass param_1 into update_and_get_output instead of __init__".
What if instead I could just write:
def update_and_get_output(last_state, new_observation, param_1, param_2)
new_state = do_some_state_update(last_state, new_observation, _param_1)
output = transform_state_to_output(last_state, _param_2)
return new_state, output
processed_things = [state, output:= update_and_get_output(state, x, param_1=1, param_2=4) -> output for x in observations from state=initial_state]
Now we have:
- No mutable objects (which cuts of a whole slew of potential bugs and anti-patterns familiar to people who do OOP.)
- Fewer lines of code
- Looser assumptions on usage and less refactoring. (if I want to now pass in param_1 at each iteration instead of just initialization, I need to make no changes to update_and_get_output).
- No need for state getters/setters, since state is is passed around explicitly.
I realize that calling for changes to syntax is a lot to ask - but I still believe that the main objections to this syntax would also have been raised as objections to the now-ubiquitous list-comprehensions - they seem hostile and alien-looking at first, but very lovable once you get used to them.