[Python-ideas] Block-Scoped Exception Handlers
cs at zip.com.au
cs at zip.com.au
Wed May 4 19:05:53 EDT 2016
On 04May2016 15:58, Kyle Lahnakoski <klahnakoski at mozilla.com> wrote:
>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 also like context from close to where the exception occurred,
while doing that catching at whatever the suitable outer layer may
be, as normal. Let me show you what I do...
I have a module "cs.logutils":
https://bitbucket.org/cameron_simpson/css/src/tip/lib/python/cs/logutils.py
which is also on PyPI, thus "pip install"able.
It has a context manager called "Pfx", short for "prefix". I use it like this:
from cs.logutils import Pfx
...
with Pfx(filename):
with open(filename) as fp:
for lineno, line in enumerate(fp, 1):
with Pfx(lineno):
... do stuff here ...
If an exception occurs within a Pfx context manager, its message
attributes get prepended with the strings of the active Pfx instances,
joined by ": ". Then it is reriased and handled exaxtly as if there
were no Pfxs in play. So if some ValueError occured while processing
line 3 of the file "foo.txt" the ValueError's message would start
"foo.txt: 3: " and proceed with the core ValueError message.
This provides me with cheap runtime context for all exceptions,
with minimal boilerplate in my code. All the catch-and-reraise stuff
happens in the __exit__ method of the innermost Pfx instance, if
an exception occurs.
When no exceptions occur all it is doing is maintaining a thread
local stack of current message prefixes.
It looks like this might address your concerns without adding things
to Python itself. You could certainly make a case for annotating
the exceptions with an arbitrary extra object with whatever structured
state eg a dict); suggestions there welcome.
What do you think of this approach to your concerns?
Cheers,
Cameron Simpson <cs at zip.com.au>
>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!
>
>
>_______________________________________________
>Python-ideas mailing list
>Python-ideas at python.org
>https://mail.python.org/mailman/listinfo/python-ideas
>Code of Conduct: http://python.org/psf/codeofconduct/
--
More information about the Python-ideas
mailing list