2010-10-22 10 views
12

Recentemente ho sono imbattuto in alcuni comportamenti sorprendenti in Python generatori:generatore di Python, non deglutire eccezione nel 'coroutine'

class YieldOne: 
    def __iter__(self): 
    try: 
     yield 1 
    except: 
     print '*Excepted Successfully*' 
     # raise 

for i in YieldOne(): 
    raise Exception('test exception') 

che fornisce l'output:

*Excepted Successfully* 
Traceback (most recent call last): 
    File "<stdin>", line 2, in <module> 
Exception: test exception 

Ero (piacevolmente) sorpreso che *Excepted Successfully* è stato stampato, poiché era ciò che volevo, ma anche sorpreso che l'eccezione si fosse propagata fino al livello più alto. Mi aspettavo di dover utilizzare la parola chiave (commentato in questo esempio) raise per ottenere il comportamento osservato.

qualcuno può spiegare il motivo per cui questa funzionalità funziona come lo fa, e perché il except nel generatore non inghiottire l'eccezione?

È questa l'unica istanza in Python in cui uno except non ingerisce un'eccezione?

risposta

14

Il tuo codice non fa quello che pensi che faccia. Non puoi generare eccezioni in una coroutine come questa. Quello che fai invece è prendere l'eccezione GeneratorExit. Vedi cosa succede quando si utilizza un diverso Eccezione:

class YieldOne: 
    def __iter__(self): 
    try: 
     yield 1 
    except RuntimeError: 
     print "you won't see this" 
    except GeneratorExit: 
     print 'this is what you saw before' 
     # raise 

for i in YieldOne(): 
    raise RuntimeError 

come questo diventa ancora upvotes, ecco come si sollevare un'eccezione in un generatore:

class YieldOne: 
    def __iter__(self): 
    try: 
     yield 1 
    except Exception as e: 
     print "Got a", repr(e) 
     yield 2 
     # raise 

gen = iter(YieldOne()) 

for row in gen: 
    print row # we are at `yield 1` 
    print gen.throw(Exception) # throw there and go to `yield 2` 

vedi Documentazione per generator.throw.

+0

Aha, ora ha senso. Inizialmente non mi aspettavo che l'eccezione si propagasse 'sopra' al generatore. – EoghanM

+0

+1 molto interessante! – rubik

+0

+1 per illuminare il trucco 'generator.throw'! – EoghanM

6

MODIFICA: cosa ha detto THC4k.

Se davvero si vuole sollevare un'eccezione arbitrario all'interno di un generatore, utilizzare il metodo throw:

>>> def Gen(): 
...  try: 
...    yield 1 
...  except Exception: 
...    print "Excepted." 
... 
>>> foo = Gen() 
>>> next(foo) 
1 
>>> foo.throw(Exception()) 
Excepted. 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
StopIteration 

Si noterà che si ottiene un StopIteration al livello più alto. Questi sono generati da generatori che hanno esaurito gli elementi; di solito vengono inghiottiti dal ciclo for ma in questo caso abbiamo fatto generare al generatore un'eccezione in modo che il ciclo non li noti.

Problemi correlati