2012-07-06 9 views
35

にスローされた例外ハンドル:ジェネレータが例外をスローした場合私は発電機とそれを消費機能持っている発電機

def read(): 
    while something(): 
     yield something_else() 

def process(): 
    for item in read(): 
     do stuff 

を、私は消費者の機能にそれを処理したいと、その後のかかる継続iteratorが消耗するまで繰り返します。ジェネレータでは例外処理コードを必要としないことに注意してください。

私のような何かについて考えた:

reader = read() 
while True: 
    try: 
     item = next(reader) 
    except StopIteration: 
     break 
    except Exception as e: 
     log error 
     continue 
    do_stuff(item) 

が、これは私にはかなり厄介になります。

答えて

38

ジェネレータが例外をスローすると、そのジェネレータは終了します。生成するアイテムを消費し続けることはできません。

例:

>>> def f(): 
...  yield 1 
...  raise Exception 
...  yield 2 
... 
>>> g = f() 
>>> next(g) 
1 
>>> next(g) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in f 
Exception 
>>> next(g) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
StopIteration 

あなたは発電機のコードをコントロールしている場合、あなたは発電機の内部で例外を処理することができます。そうでない場合は、例外が発生するのを避けてください。

+1

ありがとう!これはそのように見えます。あなたは、フォローアップの質問を見てみることができます:http://stackoverflow.com/q/11366892/989121? – georg

5

これは、正しく/エレガントに処理するとわかりません。

私が行うことは、発電機のExceptionyieldとし、それを別の場所に持ち上げることです。 Like:

class myException(Exception): 
    def __init__(self, ...) 
    ... 

def g(): 
    ... 
    if everything_is_ok: 
     yield result 
    else: 
     yield myException(...) 

my_gen = g() 
while True: 
    try: 
     n = next(my_gen) 
     if isinstance(n, myException): 
      raise n 
    except StopIteration: 
     break 
    except myException as e: 
     # Deal with exception, log, print, continue, break etc 
    else: 
     # Consume n 

このようにして、私はまだ例外を引き上げずに持ち越して、ジェネレータ機能を停止させるでしょう。主な欠点は、各繰り返しで得られた結果をisinstanceで確認する必要があることです。私は、異なるタイプの結果をもたらすことができる発電機が好きではありませんが、最後の手段として使用します。

+1

ありがとう、これは私がやったことに似ています([この回答](http://stackoverflow.com/questions/11366892/handle-generator-exceptions-in-its-consumer)を参照してください) – georg

+0

ポインティングのための@georgありがとうその答えを出しなさい。 「例外」を含む「タプル」を得ることは、よりよい解決策であると私は考えています。 – dojuba

3

私はこの問題を数回解決する必要があり、他の人が何をしているのかを探してからこの質問を受けました。

リファクタリングを少し必要とするオプションは、raiseではなく、(別のエラー処理ジェネレータの)ジェネレータの例外であるthrowになります。これは、次のようになります。

def read(handler): 
    # the handler argument fixes errors/problems separately 
    while something(): 
     try: 
      yield something_else() 
     except Exception as e: 
      handler.throw(e) 
    handler.close() 

def err_handler(): 
    # a generator for processing errors 
    while True: 
     try: 
      yield 
     except Exception1: 
      handle_exc1() 
     except Exception2: 
      handle_exc2() 
     except Exception3: 
      handle_exc3() 
     except Exception: 
      raise 

def process(): 
    handler = err_handler() 
    for item in read(handler): 
     do stuff 

これは必ずしも最良の解決策になるとは限りませんが、確かに選択肢です。

EDIT:

あなたはデコレータと少しだけよりよいそれをすべてこの方法を作る(私はこれをテストしていませんが、それは動作するはず、EDITができます動作しない、私は後でそれを修正しますが、アイデアは健全です):

def handled(handler): 
    """ 
    A decorator that applies error handling to a generator. 

    The handler argument received errors to be handled. 

    Example usage: 

    @handled(err_handler()) 
    def gen_function(): 
     yield the_things() 
    """ 
    def handled_inner(gen_f): 
     def wrapper(*args, **kwargs): 
      g = gen_f(*args, **kwargs) 
      while True: 
       try: 
        yield from g 
       except Exception as e: 
        handler.throw(e) 
     return wrapper 
    return handled_inner 

@handled(err_handler()) 
def read(): 
    while something(): 
     yield something_else() 

def process(): 
    for item in read(): 
     do stuff 
関連する問題