[Python-ideas] Block-Scoped Exception Handlers
Kyle Lahnakoski
klahnakoski at mozilla.com
Wed May 4 15:58:58 EDT 2016
Please excuse my nomenclature. I hope the community can correct the
synonyms that clarify my proposal.
Problem
-------
I program defensively, and surround many of my code blocks with try
blocks to catch expected and unexpected errors. Those unexpected
errors seem to dominate in my code; I never really know how many ways my
SQL library can fail, nor am I really sure that a particular key is in a
`dict()`. Most of the time I can do nothing about those unexpected
errors; I simply chain them, with some extra description about what the
code block was attempting to do.
I am using 2.7, so I have made my own convention for chaining
exceptions. 3.x chains more elegantly:
for t in todo:
try:
# do error prone stuff
except Exception, e:
raise ToDoError("oh dear!") from e
The “error prone stuff” can itself have more try blocks to catch known
failure modes, maybe deal with them. Add some `with` blocks and a
conditional, and the nesting gets ugly:
def process_todo(todo):
try:
with Timer("todo processing"):
# pre-processing
for t in todo:
try:
# do error prone stuff
except Exception, e:
raise TodoError("oh dear!") from e
# post-processing
except Exception, e:
raise OverallTodoError("Not expected") from e
Not only is my code dominated by exception handling, the meaningful code
is deeply nested.
Solution
--------
I would like Python to have a bare `except` statement, which applies
from that line, to the end of enclosing block (or next `except`
statement). Here is the same example using the new syntax:
def process_todo(todo):
except Exception, e:
raise OverallTodoError("Not expected") from e
with Timer("todo processing"):
# pre-processing
for t in todo:
except Exception, e:
raise TodoError("oh dear!") from e
# do error prone stuff
# post-processing
Larger code blocks do a better job of portraying he visual impact of the
reduced indentation. I would admit that some readability is lost
because the error handling code precedes the happy path, but I believe
the eye will overlook this with little practice.
Multiple `except` statements are allowed. They apply as if they were
used in a `try` statement; matched in the order declared:
def process_todo(todo):
pre_processing() # has no exception handling
except SQLException, e: # effective until end of method
raise Exception("Not expected") from e
except Exception, e:
raise OverallTodoError("Oh dear!") from e
processing()
A code block can have more than one `except` statement:
def process_todo(todo):
pre_processing() # no exception handling
except SQLException, e: # covers lines from here to beginning
of next except statement
raise Exception("Not expected") from e
except Exception, e: # catches other exception types
raise Exception("Oh dear!") from e
processing() # Exceptions caught
except SQLException, e: # covers a lines to end of method
raise Exception("Happens, sometimes") from e
post_processing() # SQLException caught, but not Exception
In these cases, a whole new block is effectively defined. Here is the
same in legit Python:
def process_todo(todo):
pre_processing() # no exception handling
try:
processing() # Exceptions caught
except SQLException, e: # covers all lines from here to
beginning of next except statement
raise Exception("Not expected") from e
except Exception, e: # catches other exception types
raise Exception("Oh dear!") from e
try:
post_processing() # SQLException caught, but not Exception
except SQLException, e: # covers a lines to end of method
raise Exception("Happens, sometimes") from e
Other Thoughts
--------------
I only propose this for replacing `try` blocks that have no `else` or
`finally` clause. I am not limiting my proposal to exception chaining;
Anything allowed in `except` clause would be allowed.
I could propose adding `except` clauses to each of the major statement
types (def, for, if, with, etc…). which would make the first example
look like:
def process_todo(todo):
with Timer("todo processing"):
# pre-processing
for t in todo:
# do error prone stuff
except Exception, e:
raise TodoError("oh dear!") from e
# post-processing
except Exception, e:
raise OverallTodoError("Not expected") from e
But, I am suspicious this is more complicated than it looks to
implement, and the `except` statement does seem visually detached from
the block it applies to.
Thank you for your consideration!
More information about the Python-ideas
mailing list