0

I often have the case where I use two variables, one of them being the "current" value of something, another one a "newly retrieved" one.

After checking for equality (and a relevant action taken), they are swapped. This is then repeated in a loop.

import time
import random

def get_new():
    # the new value is retrieved here, form a service or whatever
    vals = [x for x in range(3)]
    return random.choice(vals)

current = None
while True:
    # get a new value
    new = get_new()
    if new != current:
        print('a change!')
    else:
        print('no change :(')
    current = new
    time.sleep(1)

This solution works but I feel that it is a naïve approach and I think I remember (for "write pythonic code" series of talks) that there are better ways.

What is the pythonic way to handle such mechanism?

WoJ
  • 27,165
  • 48
  • 180
  • 345
  • 2
    Note that they aren't actually swapped; `new` always gets a fresh value, and `current` just gets the value from `new`, never vice versa. – chepner Apr 19 '18 at 15:52
  • 1
    The Pythonic idiom for swapping is `a,b = b,a`. But as @chepner pointed out, your code doesn't actually perform any swapping. – John Gordon Apr 19 '18 at 15:56

1 Answers1

2

Really, all you have is a simple iteration over a sequence, and you want to detect changes from one item to the next. First, define an iterator that provides values from get_new:

# Each element is a return value of get_new(), until it returns None.
# You can choose a different sentinel value as necessary.
sequence = iter(get_new, None)

Then, get two copies of the iterator, one to use as a source for current values, the other for new values.

i1, i2 = itertools.tee(sequence)

Throw out the first value from one of the iterators:

next(i2)

Finally, iterate over the two zipped together. Putting it all together:

current_source, new_source = tee(iter(get_new, None))
next(new_source)
for current, new in zip(current_source, new_source):
    if new != current:
        ...
    else:
        ...
    time.sleep(1)

Using itertoolz.cons:

current_source, new_source = tee(iter(get_new, None))
for current, new in zip(cons(None, current_source), new_source)):
    ... 
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I think there's room for improvement here, specifically replacing the explicit call to `next` with a clean way to prepend `None` to `current_source` instead: `current_source = chain([None], current_source)` (using the `prepend` recipe from the `itertools` documentation. YMMV. – chepner Apr 19 '18 at 16:14
  • The 3rd-party `itertoolz` module does provide `cons` as an implementation of the `prepend` recipe: `current_source = cons(None, current_source)`. – chepner Apr 19 '18 at 16:17