2010-10-22 28 views
12

Je suis récemment tombé sur un comportement surprenant générateurs Python:générateur Python, exception non déglutition 'coroutine'

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

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

Ce qui donne la sortie:

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

J'étais (agréablement) surpris que *Excepted Successfully* a été imprimé, comme c'était ce que je voulais, mais aussi surpris que l'Exception soit encore propagée au plus haut niveau. Je m'attendais à devoir utiliser le mot-clé raise (commenté dans cet exemple) pour obtenir le comportement observé.

Quelqu'un peut-il expliquer pourquoi cette fonctionnalité fonctionne comme il le fait, et pourquoi le except dans le générateur ne pas avaler l'exception?

Est-ce la seule instance en Python où un except ne pas avaler une exception?

Répondre

14

Votre code ne fait pas ce que vous pensez qu'il fait. Vous ne pouvez pas élever des exceptions dans une coroutine comme celle-ci. Ce que vous faites à la place attrape l'exception GeneratorExit. Voir ce qui se passe lorsque vous utilisez une autre exception:

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 

Comme cela devient encore upvotes, voici comment vous soulevez une exception dans un générateur:

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` 

Voir docs pour generator.throw.

+0

Aha, maintenant c'est logique. Au départ, je ne m'attendais pas à ce que l'exception soit propagée au générateur. – EoghanM

+0

+1 très intéressant! – rubik

+0

+1 pour illuminer le tour de 'generator.throw'! – EoghanM

6

EDIT: Ce que dit THC4k.

Si vous voulez vraiment soulever une exception arbitraire à l'intérieur d'un générateur, utilisez la méthode 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 

Vous remarquerez que vous obtenez un StopIteration au niveau supérieur. Ceux-ci sont soulevés par des générateurs qui ont manqué d'éléments; ils sont généralement avalés par la boucle for mais dans ce cas nous avons fait lever une exception par le générateur afin que la boucle ne les remarque pas.