Useful module to be written by a newbie

Ian Kelly ian.g.kelly at gmail.com
Wed Apr 29 15:40:51 EDT 2015


On Wed, Apr 29, 2015 at 1:03 PM, Peter Otten <__peter__ at web.de> wrote:
> I was judging from the look of your MovingAverage.
>
> I don't like the interface, it really should take an iterable so that you
> can write
>
>>>> list(moving_average([1,2,3], 2))
> [1.5, 2.5]

The problem with this is that many use cases for moving averages need
to access the current average before future items become available.
For example, a download speed indicator: it needs to average the
transfer rate over the last few seconds. There will be more transfer
rate data in the future, but it's not available yet, so you can't add
it to the iterable unless the iterable is actually some sort of
read/write buffer that can be appended to once iteration has already
started.

That seems overly complex, though; for one, you need to be careful not
to exhaust the buffer, since the iteration protocol requires that once
next() raises StopIteration, it will always raise StopIteration. But
also, why add the data to object A so that it can be consumed by
object B if you could just add it to object B directly?

So I think the iterable interface that you're describing really needs
to be a coroutine of some sort:

>>> from collections import deque
>>> def moving_average(length):
...     values = deque([(yield)], maxlen=length)
...     while True:
...         values.append((yield sum(values) / len(values)))
...
>>> mavg = moving_average(5)
>>> next(mavg)
>>> mavg.send(1)
1.0
>>> mavg.send(2)
1.5
>>> mavg.send(3)
2.0
>>> mavg.send(4)
2.5
>>> mavg.send(5)
3.0
>>> mavg.send(6)
4.0
>>> mavg.send(7)
5.0

This works, but I don't really like it. For one, our moving_average
"iterable" isn't really an iterable any more; we need to call send
instead of next, which means we can't just stick it inside a for loop.
And if we can't iterate over it, then what's the point of using a
generator? If we make it a class, then we can give it a more flexible
API.

class MovingAverage(object):

    def __init__(self, length):
        self.values = deque(maxlen=length)

    def append(self, value):
        self.values.append(value)

    def average(self):
        return sum(self.values) / len(self.values)

Which is pretty much back to where we started.



More information about the Python-list mailing list