0

I want to write a wrapper for a generator which checks, if the generator yields anything and (e.g.) raises an exception if not.

I could write:

def my_wrapper(input):
    if input is None:
        return

    found = False
    for elem in my_yielding_function(input):
        found = True
        yield elem

    if not found:
        raise MyException("Empty Generator")

Is there a more pythonic way to do that?

There is one very similar question but it's more than 10 years old - maybe things have changed?

Context:

Hard to explain - I'm using a given API function which might yield nothing but in this case my function has distinguish from empty input.

frans
  • 8,868
  • 11
  • 58
  • 132
  • 1
    Do you want to preserve the initial generator (i.e. not consuming it) ? Could you provide some context where that is necessary ? – hl037_ Sep 21 '20 at 08:03
  • If possible, yes. But if there was just some way to avoid this ugly `found` flag I would be glad, too. In my eyes `for.. else` should be for this but maybe there is some other syntactical construct? – frans Sep 21 '20 at 08:07
  • What's wrong with [the second answer](https://stackoverflow.com/a/664239/6045800) from the linked question? Seems to do exactly what you want in a pretty short, Pythonic way – Tomerikoo Sep 21 '20 at 08:14
  • My code example was not clear enough - `my_generator` is no generator but a function returning a generator. I've changed the according section. – frans Sep 21 '20 at 08:16
  • Hm, I've added this question to my question already :) The answer is no - this is why I've asked the new question – frans Sep 21 '20 at 08:22
  • Does this answer your question? [Loop over empty iterator does not raise exception](https://stackoverflow.com/questions/60056208/loop-over-empty-iterator-does-not-raise-exception) – MisterMiyagi Sep 21 '20 at 08:30
  • Can you clarify in how far the linked question does *not* answer yours? The [second answer](https://stackoverflow.com/a/664239/5349916) has now been duplicated here already. – MisterMiyagi Sep 21 '20 at 08:32

1 Answers1

3

This removes the need for a flag in addition to avoid the useless for loop. You can also adapt it as a decorator to make it possible to forward the call arguments and still be reusable

def check_if_empty_first(gen):
  it = gen() # This is optional, depends if you want to make it reusable, and it you want to call with check_if_empty_first(gen) or check_if_empty_first(gen())
  try:
    yield next(it)
  except StopIteration as e:
    raise MyException("Empty Generator") from e
  yield from it

decorator version :

from functools import wraps
def check_if_empty_first(gen):
  @wraps(gen)
  def inner(*args, **kwargs
    it = gen(*args, **kwargs)
    try:
      yield next(it)
    except StopIteration as e:
      raise MyException("Empty Generator") from e
    yield from it
  return inner
hl037_
  • 3,520
  • 1
  • 27
  • 58
  • Could probably also be used as a `@decorator` – tobias_k Sep 21 '20 at 08:15
  • Yes, I though of it too, and I edited accordingly ;) – hl037_ Sep 21 '20 at 08:18
  • this is way more complicated than I hoped it would be but together with the answers from the linked question it seems to be a legit solution - and it's reusable :) – frans Sep 21 '20 at 08:38
  • the decorator one is complicated because it's a decorator... but the first one is quite straight forward and expressive : you try to yield the first item, if you can't → it's empty. Else, you yield the remaining normally – hl037_ Sep 21 '20 at 08:42