[Python-ideas] Proposal: A Reduce-Map Comprehension and a "last" builtin
Steven D'Aprano
steve at pearwood.info
Thu Apr 5 21:18:54 EDT 2018
On Thu, Apr 05, 2018 at 12:52:17PM -0400, Peter O'Connor wrote:
> I propose a new "Reduce-Map" comprehension that allows us to write:
>
> signal = [math.sin(i*0.01) + random.normalvariate(0, 0.1) for i in range(1000)]
> smooth_signal = [average = (1-decay)*average + decay*x for x in signal
> from average=0.]
I've already commented on this proposed syntax. A few further comments
below.
> Instead of:
>
> def exponential_moving_average(signal: Iterable[float], decay: float,
> initial_value: float=0.):
> average = initial_value
> for xt in signal:
> average = (1-decay)*average + decay*xt
> yield average
What I like about this is that it is testable in isolation and re-
usable. It can be documented, the implementation changed if needed
without having to touch all the callers of that function, and the name
is descriptive.
(I don't understand why so many people have such an aversion to writing
functions and seek to eliminate them from their code.)
Here's another solution which I like, one based on what we used to call
coroutines until that term was taken for async functions. So keeping in
mind that this version of "coroutine" has nothing to do with async:
import functools
def coroutine(func):
"""Decorator to prime coroutines when they are initialised."""
@functools.wraps(func)
def started(*args, **kwargs):
cr = func(*args,**kwargs)
cr.send(None)
return cr
return started
@coroutine
def exponential_moving_average(decay=0.5):
"""Exponentially weighted moving average (EWMA).
Coroutine returning a moving average with exponentially
decreasing weights. By default the decay factor is one half,
which is equivalent to averaging each value (after the first)
with the previous moving average:
>>> aver = exponential_moving_average()
>>> [aver.send(x) for x in [5, 1, 2, 4.5]]
[5, 3.0, 2.5, 3.5]
"""
average = (yield None)
x = (yield average)
while True:
average = decay*x + (1-decay)*average
x = (yield average)
I wish this sort of coroutine were better known and loved. You
can run more than one of them at once, you can feed values into
them lazily, they can be paused and put aside to come back
to them later, and if you want to use them eagerly, you can just drop
them into a list comprehension.
--
Steve
More information about the Python-ideas
mailing list