Generators and propagation of exceptions

r nbs.public at gmail.com
Fri Apr 8 21:47:51 CEST 2011


On Sat, Apr 9, 2011 at 3:22 AM, Raymond Hettinger <python at rcn.com> wrote:
>
> You could just let the exception go up to an outermost control-loop
> without handling it at all on a lower level.  That is what exceptions
> for you: terminate all the loops, unwind the stacks, and propagate up
> to some level where the exception is caught:
>
>  while 1:
>     try:
>        results = evaluator-generator(stream-of-parsed-expressions)
>        for result in results:
>            print(result)
>     except Exception as e:
>        handle_the_exception(e)

I've been thinking about something like this but the problem with
shutting down the generators is that I lose all the state information
associated with them (line numbers, have to reopen files if in batch
mode etc.). It's actually more difficult than my current solution.

> OTOH, If you want to catch the exception at the lowest level and wrap
> it up as data (the ErrorResult in your example), there is a way to
> make it more convenient.  Give the ErrorResult object some identify
> methods that correspond to the methods being called by upper levels.
> This will let the object float through without you cluttering each
> level with detect-and-reraise logic.

I'm already making something like this (that is, if I understand you
correctly). In the example below (an "almost" real code this time, I
made too many mistakes before) all the Expressions (including the
Error one) implement an 'eval' method that gets called by one of the
loops. So I don't have to do anything to detect the error, just have
to catch it and reraise it at each stage so that it propagates to the
next level (what I have to do anyway as the next level generates
errors as well).

class Expression(object):
    def eval(self):
        pass

class Error(Expression):
    def __init__(self, exception):
        self.exception = exception

    def eval(self):
        raise self.exception

def parseTokens(self, tokens):
    for token in tokens:
        try:
            yield Expression.parseToken(token, tokens)
        except ExpressionError as e:
# here is where I wrap exceptions raised during parsing and embed them
            yield Error(e)

def eval(expressions, frame):
    for expression in expressions:
        try:
# and here (.eval) is where they get unwrapped and raised again
            yield unicode(expression.eval(frame))
        except ExpressionError as e:
# here they are embedded again but because it is the last stage
# text representation is fine
            yield unicode(e)


>   class ErrorResult:
>       def __iter__(self):
>           # pass through an enclosing iterator
>           yield self
>
> Here's a simple demo of how the pass through works:
>
>    >>> from itertools import *
>    >>> list(chain([1,2,3], ErrorResult(), [4,5,6]))
>    [1, 2, 3, <__main__.ErrorResult object at 0x2250f70>, 4, 5, 6]

I don't really understand what you mean by this example. Why would
making the Error iterable help embedding it into data stream? I'm
currently using yield statement and it seems to work well (?).

Anyway, thank you all for helping me out and bringing some ideas to
the table. I was hoping there might be some pattern specifically
designed for thiskind of job (exception generators anyone?), which
I've overlooked. If not anything else, knowing that this isn't the
case, makes me feel better about the solution I've chosen.

Thanks again,

-r



More information about the Python-list mailing list