0

It's a follow-up to my 1 generator -- multiple consumers question. As StopIteration is the way the generator signals its exhaustion, unfortunately, I now have many exception-handling code littered all over the place in the client code (for every next() statement in the example below).

Is there a better way to exit with whatever value is built in meal upon hitting the first StopIteration exception?

def client(course, take):
    meal = []
    for _ in range(take):
        try:
            some_meal = next(course)
            meal.append(some_meal)
        except StopIteration:
            pass
    if take % 2 == 0:
        try:
            some_meal = next(course)
            meal.append(some_meal)
        except StopIteration:
            pass
    return meal

UPDATE Eventually, I ended up using 'itertools.islice' (see accepted solution below) as this function takes care of the StopIteration itself (see the for-loop equivalent implementation shown in the itertools doc. I prefer this solution over using next default second argument as it would imply checking each meal (still, I'd better use the latter than all the exception handling above).

Community
  • 1
  • 1
green diod
  • 1,399
  • 3
  • 14
  • 29

2 Answers2

2

Just return directly in the first exception handler:

def client(course, take):
    meal = []
    for _ in range(take):
        try:
            some_meal = next(course)
            meal.append(some_meal)
        except StopIteration:
            return meal
    if take % 2 == 0:
        try:
            some_meal = next(course)
            meal.append(some_meal)
        except StopIteration:
            pass
    return meal

although I'd still use the standard library more here and not have to catch those StopIteration exceptions nearly as much:

from itertools import islice


def client(course, take):
    meal = list(islice(course, take))

    if take % 2 == 0:
        some_meal = next(course, None)
        if some_meal is not None:
            meal.append(some_meal)

    return meal
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • So, you think there is no better way than adding exception-handling everywhere? – green diod Mar 03 '14 at 17:04
  • You already had the exception handling in this method; all I did was show you where you could use `return`.. – Martijn Pieters Mar 03 '14 at 17:08
  • And in this case, I'd still use `islice()` anyway, because then you don't have to catch `StopIteration` at all. – Martijn Pieters Mar 03 '14 at 17:09
  • My code already worked but I don't like all those `try...except` statements scattered all over the place. So your suggestion is to replace each `next` with `islice`? – green diod Mar 03 '14 at 17:16
  • @greendiod: Yes, or use `next(iterable, default)` to return a default value instead of raising `StopIteration`. – Martijn Pieters Mar 03 '14 at 17:19
  • I can't see a reasonable default as I don't want add anything at all when the generator is exhausted. A `None` or an empty list would be appended and most importantly, the `client` would continue instead of returning right away (Sorry, I don't know how to reference your @name, the tab completion seems not working) – green diod Mar 03 '14 at 17:25
  • I used `None` as the default, then test for it; you could use `sentinel = object(); some_meal = next(course, sentinel); if some_meal is not sentinel:` to allow for *any* value in the iterable to be appended, including `None`. (You don't need to reference me, I am pinged always on my posts) – Martijn Pieters Mar 03 '14 at 17:28
  • Your choices are either to catch the `StopIteration` to detect early that your generator is exhausted, or to ignore that the generator is empty and handle `next()` calls with a sentinel. – Martijn Pieters Mar 03 '14 at 17:29
  • Ok, that will be it. I'll islice everywhere (even one by one if needed). Too bad the `islice` doc is so short. – green diod Mar 03 '14 at 17:33
0

Besides using islice, if I read the code correctly then it can be made even simpler:

def client(course, take):
    if take % 2 == 0:
        take += 1
    return list(itertools.islice(course, take))
RemcoGerlich
  • 30,470
  • 6
  • 61
  • 79
  • Ok but no, it was just an oversimplified example. My point was that there could be many `next` statements in `client` – green diod Mar 03 '14 at 18:22