2010-10-22 7 views
12

Hace poco se encontró con un comportamiento extraño en los generadores de Python:generador de Python, no tragar excepción en 'corrutina'

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

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

que da la salida:

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

yo era (agradablemente) sorprendido que se imprimió *Excepted Successfully*, ya que esto era lo que quería, pero también me sorprendió que la Excepción todavía se propagara al nivel superior. Esperaba tener que usar la palabra clave raise (comentada en este ejemplo) para obtener el comportamiento observado.

¿Alguien puede explicar por qué esta funcionalidad funciona como lo hace, y por qué except en el generador no se traga la excepción?

¿Es esta la única instancia en Python donde except no traga una excepción?

Respuesta

14

Tu código no hace lo que crees que hace. No puedes subir excepciones en una corutina como esta. Lo que hace en su lugar es detectar la excepción GeneratorExit. Vea lo que sucede cuando se utiliza una excepción diferente:

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 

Como esto todavía se pone upvotes, aquí es la forma de lanzar una excepción en un generador:

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` 

Ver documentos para generator.throw.

+0

Aha, ahora tiene sentido. Originalmente no esperaba que la excepción se propagara 'sobre' al generador. – EoghanM

+0

+1 muy interesante! – rubik

+0

+1 para iluminar el truco 'generator.throw' – EoghanM

6

EDITAR: Lo que dijo THC4k.

Si realmente desea elevar una excepción arbitraria dentro de un generador, utilice el método 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 

Se dará cuenta de que usted consigue una StopIteration en el nivel superior. Estos son planteados por generadores que se han quedado sin elementos; generalmente son tragados por el bucle for, pero en este caso hicimos que el generador generara una excepción para que el bucle no los notara.