try..yield..finally problem: a proposed solution.
Michael Sparks
michaels at rd.bbc.co.uk
Fri Jun 6 06:30:45 EDT 2003
Taking your suggestion, you get a cleaned up version which is
pretty elegant:
def finfunc():
try:
try:
# Risky code, e.g.
for i in xrange(10):
t=i/random.randint(0,10)
yield "data",i
raise Finality
except:
print "Finally"
raise
except Finality:
pass
for v in finfunc():
print v
However, you find that *if* your code is of the form:
# Form 1
try:
...
yield
raise Finality
except:
raise
and not of the form:
# Form 2
try:
...
yield
raise Finality
except:
yield ### You're going to hate this...
raise ### Line A
Then you find this works suggestion works fine, for all exceptions - those
deriving from the Exception class, or otherwise. If you use form 2 however
it breaks horribly.
If you use the original (form 3 for convenience):
# Form 3
try:
yield
except Exception,x:
yield
raise x
Then this always works... for exceptions deriving from the Exception
class. (and breaks with any legacy code)
What breaks?
If you use form 2, when you yield, the "current exception" is cleared.
ie when you do the raise on Line A you get a 'exceptions must not be of
NoneType' error. Clearly whereever this current exception is stored, it
either isn't in the python stack frame that's put aside when you yield,
or it's explicitly cleared. This strikes me as either a bug, or an
undocumented "boundary feature".
Minimal test case:
def finfunc():
try:
yield 1
raise Exception
except:
yield 2
raise
for v in finfunc():
print v
However, in Form 3, we catch and bind the exception to x, so that despite
the current exception being cleared we can still rethrow, but of course
you lose the benefits of the stack trace pointing at the right place...
Form 3 however has the disadvantage that because it doesn't catch all
exceptions, it means that you don't have the same try...finally semantics
that have with Form 1. (Some exceptions - even if they're not stylistically
nice - can still get through and cause breakage.)
Having played with this for a few days, and reading the discussion I think
that the decision to disallow:
try:
yield
finally:
#stuff
Is a good one - it results in too great a likelyhood of people shooting
themselves in the foot.
However the cleanest alternative:
try:
yield
except:
#stuff
raise
Suffers from the downside that if you yield inside the except clause that
the current exception is cleared. (Which strikes me as a bug, since you'd
expect the runtime state in the generator not to changed after a yield.
Also I'd view it as a bug since otherwise I can write the code looking
cleaner in C ;)
Form 1 is the nicest from many perspectives, but Form 3 always works. I'm not
sure of the best way of resolving this, or whether to just use my original
non-try-finally versions instead now :-)
Michael
--
Michael.Sparks at rd.bbc.co.uk
British Broadcasting Corporation, Research and Development
Kingswood Warren, Surrey KT20 6NP
This message (and any attachments) may contain personal views
which are not the views of the BBC unless specifically stated.
More information about the Python-list
mailing list