Alfe
Alfe

Reputation: 59426

Keeping try block small when catching exceptions in generator

How can I keep the try block as small as possible when I have to catch an exception which can occur in a generator?

A typical situation looks like this:

for i in g():
  process(i)

If g() can raise an exception I need to catch, the first approach is this:

try:
  for i in g():
    process(i)
except SomeException as e:
  pass  # handle exception ...

But this will also catch the SomeException if it occurs in process(i) (this is what I do not want).

Is there a standard approach to handle this situation? Some kind of pattern?

What I am looking for would be something like this:

try:

  for i in g():

except SomeException as e:
  pass  # handle exception ...

    process(i)

(But this is syntactic nonsense of course.)

Upvotes: 5

Views: 387

Answers (4)

Alfe
Alfe

Reputation: 59426

The straight-forward approach for this seems to be unwrap the for construct (which makes it impossible to catch exceptions just in the generator, due to its syntax) into its components.

gen = g()
while True:
  try:
    i = gen.next()
  except StopIteration:
    break
  process(i)

Now we can just add our expected exception to the try block:

gen = g()
while True:
  try:
    i = gen.next()
  except StopIteration:
    break
  except SomeException as e:
    pass  # handle exception ...
    break
  process(i)

Does that (besides it being ugly as hell) have disadvantages? And more: Is there a nicer solution?

(I won't accept my own answer because it being ugly, but maybe others like and upvote it.)

Upvotes: 2

ecatmur
ecatmur

Reputation: 157344

You could convert exceptions occurring in the inner block:

class InnerException(Exception):
  pass

try:
  for i in g():
    try:
      process(i)
    except Exception as ex:
      raise InnerException(ex)
except InnerException as ex:
  raise ex.args[0]
except SomeException as e:
  pass  # handle exception ...

Another option is to write a local generator that wraps g:

def safe_g():
  try:
    for i in g():
      yield i
  except SomeException as e:
    pass  # handle exception ...
for i in safe_g():
  process(i)

Upvotes: 3

Holger
Holger

Reputation: 2175

I don't know, if it works. You could evaluate g() into a list. I can't test it, because I don't have an iterator that throws an exception at hand.

try:
    glist = list(g())
except SomeException as e:
    pass  # handle exception ...
for i in glist:
    process(i)

Upvotes: 0

warvariuc
warvariuc

Reputation: 59594

In your generator raise a different kind of exception, that you will be able to distinguish.

class GeneratorError(Exception):
    pass

def g():
    try:
        yield <smth>
    except:
        raise GeneratorError

try:
  for i in g():
    process(i)
except GeneratorError:
    pass  # handle generator error
except SomeException as e:
  pass  # handle exception .

Upvotes: 1

Related Questions