Previous in yield operations - python
Asked Answered
P

5

5

Recently i have been using the 'yield' in python. And I find generator functions very useful. My query is that, is there something which could decrement the imaginative cursor in the generator object. Just how next(genfun) moves and outputs +i'th item in the container, i would like to know if there exists any function that may call upon something like previous(genfun) and moves to -1th item in the conatiner.

Actual Working

def wordbyword():
  words = ["a","b","c","d","e"]
  for word in words:
    yield word

getword = wordbyword()

next(getword)
next(getword)

Output's

a
b

What I would like to see and achieve is

def wordbyword():
  words = ["a","b","c","d","e"]
  for word in words:
    yield word

getword = wordbyword()

next(getword)
next(getword)
previous(getword)

Expected Output

a
b
a

This may sound silly, but is there someway there is this previous in generator, if not why is it so?. Why not we could decrement the iterator, or am I ignorant of an existing method, pls shower some light. What can be the closest way to implement what I have here in hand.

Photolysis answered 6/6, 2021 at 11:56 Comment(1)
There's also itertools.tee, which allows you to iterate over something multiple times. Really, it's just storing the intermediates in a list behind the scenes, but it's as close as you're going to get to multi-pass generators in Python.Gathers
R
4

No there is no such function to sort of go back in a generator function. The reason is that Python does not store up the previous value in a generator function natively, and as it does not store it, it also cannot perform a recalculation.

For example, if your generator is a time-sensitive function, such as

def time_sensitive_generator():
    yield datetime.now()

You will have no way to recalculate the previous value in this generator function.

Of course, this is only one of the many possible cases that a previous value cannot be calculated, but that is the idea.

If you do not store the value yourself, it will be lost forever.

Raybourne answered 6/6, 2021 at 12:4 Comment(0)
C
2

As already said, there is no such function since the entire point of a generator is to have a small memory footprint. You would need to store the result.

You could automate the storing of previous results. One use-case of generators is when you have a conceptually infinite list (e.g. that of prime numbers) for which you only need an initial segment. You could write a generator that builds up these initial segments as a side effect. Have an optional history parameter that the generator appends to while it is yielding. For example:

def wordbyword(history = None):
  words = ["a","b","c","d","e"]
  for word in words:
      if isinstance(history,list): history.append(word)
      yield word

If you use the generator without an argument, getword = wordbyword(), it will work like an ordinary generator, but if you pass it a list, that list will store the growing history:

hist = []
getword = wordbyword(hist)

print(next(getword)) #a
print(next(getword)) #b
print(hist) #['a','b']
Counterfoil answered 6/6, 2021 at 12:23 Comment(0)
T
2

Iterating over a generator object consumes its elements, so there is nothing to go back to after using next. You could convert the generator to a list and implement your own next and previous

index = 0


def next(lst):
    global index
    index += 1
    if index > len(lst):
        raise StopIteration
    return lst[index - 1]


def previous(lst):
    global index
    index -= 1
    if index == 0:
        raise StopIteration
    return lst[index - 1]

getword = list(wordbyword())
print(next(getword)) # a
print(next(getword)) # b
print(previous(getword)) # a
Tenebrific answered 6/6, 2021 at 12:24 Comment(0)
H
2

One option is to wrap wordbyword with a class that has a custom __next__ method. In this way, you can still use the built-in next function to consume the generator on-demand, but the class will store all the past results from the next calls and make them accessible via a previous attribute:

class save_last:
   def __init__(self, f_gen):
      self.f_gen = f_gen
      self._previous = []
   def __next__(self):
      self._previous.append(n:=next(self.i_gen))
      return n
   def __call__(self, *args, **kwargs):
      self.i_gen = self.f_gen(*args, **kwargs)
      return self
   @property
   def previous(self):
      if len(self._previous) < 2:
         raise Exception
      return self._previous[-2] 

@save_last
def wordbyword():
   words = ["a","b","c","d","e"]
   for word in words:
      yield word

getword = wordbyword()
print(next(getword))
print(next(getword))
print(getword.previous)

Output:

a
b
a
Heliotropism answered 6/6, 2021 at 16:0 Comment(0)
G
0

You can use Generator.send to « tell » to the generator : show me the previous one. This is a complete example for Generator.

from collections.abc import Generator


def plop() -> Generator[str, bool, bytes]:
    words = ["a","b","c","d","e"]
    i = 0
    word = words[i]

    while i < len(words):
        previous = yield word
        if previous:
            word = words[i]
        else:
            i += 1
            word = words[i]

    return b"FINISHED"


g = plop()
word = next(g)  # a - first iteration must be next(), not just for this specif case
word = next(g)  # b
word = next(g)  # c
word = g.send(True)  # c
word = next(g)  # d
word = next(g)  # e
word = next(g)  # raise StopIteration(b"FINISHED")
Grassofparnassus answered 28/2, 2024 at 13:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.