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?

2
  • 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. Commented Apr 19, 2018 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. Commented Apr 19, 2018 at 15:56

1 Answer 1

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)):
    ... 
Sign up to request clarification or add additional context in comments.

2 Comments

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.
The 3rd-party itertoolz module does provide cons as an implementation of the prepend recipe: current_source = cons(None, current_source).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.