2

The code below is from the book "Functional Python Programming, Steven F. Lott" however I can't handle the exception and run the code as expected.

How can I handle the "RuntimeError: generator raised StopIteration"?

Note: I tried googling and read all the results I found from stackoverflow.

Thanks.

from typing import Callable, Iterable, Tuple, Iterator, Any

def group_by_iter(n: int, items: Iterator) -> Iterator[Tuple]:
    """
    .>>> list( group_by_iter( 7, filter( lambda x: x%3==0 or x%5==0, range(1,50) ) ) )
    [(3, 5, 6, 9, 10, 12, 15), (18, 20, 21, 24, 25, 27, 30), (33, 35, 36, 39, 40, 42, 45), (48,)]
    """
    row = tuple(next(items) for i in range(n))
    while row:
        yield row
        row = tuple( next(items) for i in range(n) )

print( list( group_by_iter( 7, filter( lambda x: x%3==0 or x%5==0, range(1,50) ) ) ) )
muc
  • 23
  • 3
  • What is the exception? – 9769953 Dec 06 '22 at 08:56
  • Exception is "RuntimeError: generator raised StopIteration". – muc Dec 06 '22 at 08:57
  • 2
    This code relies on old, pre-[PEP 479](https://peps.python.org/pep-0479/) behavior of `StopIteration` propagating out of generators. It won't work on Python 3.7 or up. – user2357112 Dec 06 '22 at 08:59
  • Thanks, you are right. This didn't work but is there any simple solution to work as expected. I tried the row = tuple ... inside try, exception but this didn't catch the last value. – muc Dec 06 '22 at 09:00
  • @user2357112 Which is funny, considering that typing, used here, was introduced in Python 3.5. – 9769953 Dec 06 '22 at 09:00
  • @9769953: Sorry, got the Python version mixed up - the behavior change is behind a `__future__` import on Python 3.5. It only became the default in 3.7. – user2357112 Dec 06 '22 at 09:02
  • 3
    Does this answer your question? [How to get the n next values of a generator in a list (python)](https://stackoverflow.com/questions/4152376/how-to-get-the-n-next-values-of-a-generator-in-a-list-python) – gre_gor Dec 06 '22 at 09:13
  • @gre_gor: the answer I am looking is the answer below, thanks. – muc Dec 06 '22 at 11:01

1 Answers1

1

The built-in next() function can take a second argument which is the default value returned when the iterator is exhausted. You could exploit this functionality to avoid the StopIteration error by returning None by default and filtering it from the result.

Change tuple(next(items) for _ in range(n)) to tuple(e for e in [next(items, None) for _ in range(n)] if e is not None) and the final tuple will be generated cleanly.

from typing import Callable, Iterable, Tuple, Iterator, Any

def group_by_iter(n: int, items: Iterator) -> Iterator[Tuple]:
    """
    .>>> list( group_by_iter( 7, filter( lambda x: x%3==0 or x%5==0, range(1,50) ) ) )
    [(3, 5, 6, 9, 10, 12, 15), (18, 20, 21, 24, 25, 27, 30), (33, 35, 36, 39, 40, 42, 45), (48,)]
    """
    row = tuple(next(items) for _ in range(n))
    while row:
        yield row
        row = tuple(e for e in [next(items, None) for _ in range(n)] if e is not None)

print( list( group_by_iter( 7, filter( lambda x: x%3==0 or x%5==0, range(1,50) ) ) ) )

Output:

[(3, 5, 6, 9, 10, 12, 15), (18, 20, 21, 24, 25, 27, 30), (33, 35, 36, 39, 40, 42, 45), (48,)]
Dan Nagle
  • 4,384
  • 1
  • 16
  • 28