[Python-ideas] Block-Scoped Exception Handlers

Kyle Lahnakoski klahnakoski at mozilla.com
Fri May 6 15:15:12 EDT 2016


On 5/6/2016 1:44 PM, Ethan Furman wrote:
> -1 on the whole idea.
>
> It's make code much less readable, and is way too implicit for my
> tastes, and, I think, for Python.
>
> My own dbf [1] module is roughly 10,000 lines, and I maintain a
> private copy of OpenERP which is at least 10 times that size, and no
> where does either system have so many nested try/except handlers.

This is a good point.  There are probably domains that have clear
inputs, or have a mature codebase, with tests covering all input
permutations.  These domains do not need `try` statements to cover the
unknown.  Maybe these domains dominate the universe of source code, and
I am in the minority.   I can easily be convince this is the case:  I
have seen lots of code that really does not care if an exception gets
raised.  Understanding why it failed is a real pain, for there are no
descriptions, no summary, original causes are not chained, or if they
are, the stack trace is missing.  My code has no hope of mitigating
those errors: It can not retry on HTTP errors, or provide useful
feedback if the original cause is a missing file.

Your strategy of simply not using `try` statements, may also work. 
Although, I do not know how you trace down the cause of errors on
production systems easily without them.  Mature software will not have
as many logic errors as my green code, so the cost of chasing down a
problem is better amortized, and it more reasonable to leave out `try`
blocks.



For example, from ver_33.py (in Record._retrieve_field_value),

________try:
____________if null_data[byte] >> bit & 1:
________________return Null
________except IndexError:
____________print(null_data)
____________print(index)
____________print(byte, bit)
____________print(len(self._data), self._data)
____________print(null_def)
____________print(null_data)
____________raise

It is not obvious to me that IndexError is the only Exception that can
come from here.  This code may raise file access exceptions, HTTP
exceptions, I do not know.   I would, at least, add a `raise Exception`
clause to catch those unknown situations.   Furthermore, since I do not
know how deep the stack will be on those exceptions, I would
chain-and-raise

________try:
____________if null_data[byte] >> bit & 1:
________________return Null
________except Exception, e:
____________raise CantGetValueFromFieldException(
________________null_data,
________________index,
________________byte, bit,
________________(len(self._data), self._data),
________________null_def,
________________null_data
____________) from e

This is better than letting the SQLExceptions just propagate; I have
described what I am doing (CantGetValueFromFieldException), so I can
switch on it in a later exception handler, and I still have the original
cause, which I can switch on also.

Looking at Record.__getattr__(), and Record.__getitem__(), they use the
above method, and can raise any number of other exceptions.  This means
the code that uses this library must catch those possible exceptions. 
Too many for me to keep track of now.  I will catch them all, make some
decisions, and re-raise the ones I can not deal with.

def do_stuff(**kwargs):
____try:
________my_data = Record(**kwargs)
________value = my_data["value"]
________send(value)
____catch Exception, e:
________if in_causal_chain(SQLException, e):
____________Log.warning("Database problem, not doing anymore stuff",
cause=e)
________elif in_causal_chain(CantGetValueFromFieldException, e):
____________raise AppearsToBeADataAccessProblem() from e
________else:
____________raise AlternatProblem() from e        


Thank you for the time you spend on this subject.






More information about the Python-ideas mailing list