On 23Jun2017 06:55, Steven D'Aprano email@example.com wrote:
On Thu, Jun 22, 2017 at 10:30:57PM +0200, Sven R. Kunze wrote:
We usually teach our newbies to catch exceptions as narrowly as possible, i.e. MyModel.DoesNotExist instead of a plain Exception. This works out quite well for now but the number of examples continue to grow where it's not enough.
(1) Under what circumstances is it not enough?
I believe that he means that it isn't precise enough. In particular, "nested exceptions" to me, from his use cases, means exceptions thrown from within functions called at the top level. I want this control too sometimes.
try: foo(bah) except IndexError as e: ... infer that there is no bah ...
Of course, it is possible that bah existed and that foo() raised an IndexError of its own. One might intend some sane handling of a missing bah but instead silently conceal the IndexError from foo() by mishandling it as a missing bah.
Naturally one can rearrange this code to call foo() outside that try/except, but that degree of control often leads to quite fiddly looking code with the core flow obscured by many tiny try/excepts.
One can easily want, instead, some kind of "shallow except", which would catch exceptions only if they were directly raised from the surface code; such a construct would catch the IndexError from a missing bah in the example above, but _not_ catch an IndexError raised from deeper code such within the foo() function.
Something equivalent to:
try: foo(bah) except IndexError as e: if e.__traceback__ not directly from the try..except lines: raise ... infer that there is no bah ...
There doesn't seem to be a concise way to write that. It might not even be feasible at all, as one doesn't have a way to identify the line(s) within the try/except in a form that one can recognise in a traceback.
I can imagine wanting to write something like this:
try: foo(bah) except shallow IndexError as e: ... deduce that there is no bah ...
Note that one can then deduce the missing bah instead of inferring it.
Obviously the actual syntax above is a nonstarter, but something that succinct and direct would be very handy.
The nested exception issue actually bites me regularly, almost always with properties. The property system appears designed to allow one to make "conditional" properties, which appear to exist only in some circumstances. I wrote one of them just the other day, along the lines of:
@property def target(self): if len(self.targets) == 1: return self.targets raise AttributeError('only exists when this has exactly one target')
However, more commonly I end up hiding coding errors with @property, particularly nasty when the coding error is deep in some nested call. Here is a nondeep example based on the above:
@property def target(self): if len(self.targgets) == 1: return self.targets raise AttributeError('only exists when this has exactly one target')
Here I have misspelt ".targets" as ".targgets". And quietly the .target property is simply missing, and a caller may then infer, incorrectly, things about the number of targets. What I, as the coder, actually wanted was for the errant .targgets reference to trigger something different from Attribute error, something akin to a NameError. (Obviously it _is_ a missing attribute and that is what AttributeError is for, but within a property that is ... unhelpful.)
This is so common that I actually keep around a special hack:
def prop(func): ''' The builtin @property decorator lets internal AttributeErrors escape. While that can support properties that appear to exist conditionally, in practice this is almost never what I want, and it masks deeper errors. Hence this wrapper for @property that transmutes internal AttributeErrors into RuntimeErrors. ''' def wrapper(*a, **kw): try: return func(*a, **kw) except AttributeError as e: e2 = RuntimeError("inner function %s raised %s" % (func, e)) if sys.version_info >= 3: try: eval('raise e2 from e', globals(), locals()) except: # FIXME: why does this raise a SyntaxError? raise e else: raise e2 return property(wrapper)
and often define properties like this:
from cs.py.func import prop ....... @prop def target(self): if len(self.targgets) == 1: return self.targets raise AttributeError('only exists when this has exactly one target')
Same shape, better semantics from a debugging point of view.
This is just one example where "nested" exceptions can be a misleading behavioural symptom.
Chris showed how to deal with 3). Catching nested exception is not what people want many times.
Isn't it? Why not? Can you explain further?
I hope this real world example shows why the scenario is real, and that my discussion shows why for me at least it would be handy to _easily_ catch the "shallow" exception only.
Cheers, Cameron Simpson firstname.lastname@example.org