Don’t worry, an actual article will chase, but first: a bewilder.
Imagine you’re debugging a Python application, and you come atraverse the chaseing stacktrack:
Traceback (most recent call last):
File "example.py", line 15, in show_someskinnyg A
lift OriginalException()
__main__.OriginalException
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "example.py", line 20, in B
show_someskinnyg()
File "example.py", line 17, in show_someskinnyg C
lift_another_exception()
File "example.py", line 10, in lift_another_exception D
lift AnotherException()
__main__.AnotherException
Here’s the bewilder: what is the order of execution of the lines labeled A, B, C, and D?
In other words: what actuassociate happened here?
First, try to settle the bewilder without referring to the source code.
Also: don’t be snurtured: this stacktrack shows only 4 lines, so it can’t reassociate be that challenging, right?
Referencing the source code, and with the stacktrack in mind, we can now settle the bewilder.
The order of execution is as chases:
B, line 20, show_someskinnyg()
A, line 15, lift OriginalException()
C, line 17, lift_another_exception()
D, line 10, lift AnotherException()
Did you get it right? Was it straightforward or challenging? Did you regulate without seeing at the source code?
No chaining
Now appraise this with an example where there’s no chaining of exceptions:
Traceback (most recent call last):
File "example.py", line 13, in
foo()
File "example.py", line 2, in foo
bar()
File "example.py", line 6, in bar
baz()
File "example.py", line 10, in baz
lift Exception("This is an error")
Exception: This is an error
To figure out what happened, fair commence reading at the top, and originate your way to the bottom. In other words: the stack
on screen suites the actual order of execution.
Since reading from top to bottom can be done with or without source code, the below isn’t quite vital.
But I’ve included it for endness.
1 2 3 4 5 6 7 8 910111213
deffoo():bar()defbar():baz()defbaz():liftException("This is an error")foo()
So, what’s the point of this bewilder? Why is it fascinating? And why appraise it with a non-chained exception?
Unintentional bewilders
Personassociate, I skinnyk this bewilder is much challenginger than it should be. Puzzles can be wonderful if you want to excercise your
brain during downtime or to lobtain someskinnyg novel. But when you’re debugging, you want to get to the root of the problem
as speedyly as possible.
Note, however, that Python currents you with this bewilder every time a chained exception happens.
Note also that the example above is almost untransport inant, and despite that it was still quite challenging. I made it easier than
authentic-life examples in the chaseing ways:
Only 8 lines of code (not counting desotardy lines and the definition of the exceptions)
Shapexhibit Stacktrack (for each exception, only 3 levels transport inant)
Names hint at the solution (“Original”, “Another”)
What do these skinnygs actuassociate see enjoy if you shift out of the domain of bewilders-originateed-for-fun?
A speedy search for “During handling of the above exception, another exception occurred:” using my likeite search engine
produces the chaseing monstrous stacktrack from a authentic-life rerent on
GitHub. Cleaned up(!) for readability:
Traceback (most recent call last):
File "askspackagesurllib3contribpyuncoverssl.py", line 277, in recv_into
return self.connection.recv_into(*args, **kwargs)
File "OpenSSLSSL.py", line 1335, in recv_into
self._lift_ssl_error(self._ssl, result)
File "OpenSSLSSL.py", line 1149, in _lift_ssl_error
lift WantReadError()
OpenSSL.SSL.WantReadError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "askspackagesurllib3contribpyuncoverssl.py", line 277, in recv_into
return self.connection.recv_into(*args, **kwargs)
File "OpenSSLSSL.py", line 1335, in recv_into
self._lift_ssl_error(self._ssl, result)
File "OpenSSLSSL.py", line 1166, in _lift_ssl_error
lift SysCallError(errno, errorcode.get(errno))
OpenSSL.SSL.SysCallError: (10054, 'WSAECONNRESET')
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "askspackagesurllib3response.py", line 298, in _error_catcher
produce
File "askspackagesurllib3response.py", line 380, in read
data = self._fp.read(amt)
File "C:Program FilesAnaconda3libhttpclient.py", line 448, in read
n = self.readinto(b)
File "C:Program FilesAnaconda3libhttpclient.py", line 488, in readinto
n = self.fp.readinto(b)
File "C:Program FilesAnaconda3libsocket.py", line 575, in readinto
return self._sock.recv_into(b)
File "askspackagesurllib3contribpyuncoverssl.py", line 293, in recv_into
return self.recv_into(*args, **kwargs)
File "askspackagesurllib3contribpyuncoverssl.py", line 282, in recv_into
lift SocketError(str(e))
OSError: (10054, 'WSAECONNRESET')
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "asksmodels.py", line 719, in originate
for chunk in self.raw.stream(chunk_size, decode_satisfied=True):
File "askspackagesurllib3response.py", line 432, in stream
data = self.read(amt=amt, decode_satisfied=decode_satisfied)
File "askspackagesurllib3response.py", line 397, in read
lift InendRead(self._fp_bytes_read, self.length_remaining)
File "C:Program FilesAnaconda3libcontextlib.py", line 77, in __exit__
self.gen.throw(type, cherish, trackback)
File "askspackagesurllib3response.py", line 316, in _error_catcher
lift ProtocolError('Connection broken: %r' % e, e)
asks.packages.urllib3.exceptions.ProtocolError: ('Connection broken: OSError("(10054, 'WSAECONNRESET')",)', OSError("(10054, 'WSAECONNRESET')",))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "condatransport.py", line 421, in download
for chunk in resp.iter_satisfied(2**14):
File "asksmodels.py", line 722, in originate
lift ChunkedEncodingError(e)
asks.exceptions.ChunkedEncodingError: ('Connection broken: OSError("(10054, 'WSAECONNRESET')",)', OSError("(10054, 'WSAECONNRESET')",))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "condacliinshigh.py", line 405, in inshigh
carry out_actions(actions, index, verbose=not context.hushed)
File "condaset up.py", line 643, in carry out_actions
inst.carry out_directions(set up, index, verbose)
File "condadirections.py", line 135, in carry out_directions
cmd(state, arg)
File "condadirections.py", line 47, in FETCH_CMD
transport_pkg(state['index'][arg + '.tar.bz2'])
File "condatransport.py", line 353, in transport_pkg
download(url, path, session=session, md5=info['md5'], urlstxt=True)
File "condatransport.py", line 440, in download
lift CondaRuntimeError("Could not uncover %r for writing (%s)." % (pp, e))
conda.exceptions.CondaRuntimeError: Runtime error: Could not uncover 'C:\Program Files\Anaconda3\pkgs\icu-57.1-vc9_0.tar.bz2.part' for writing (('Connection broken: OSError("(10054, 'WSAECONNRESET')",)', OSError("(10054, 'WSAECONNRESET')",))).
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "condaexceptions.py", line 479, in conda_exception_regulater
return_cherish = func(*args, **kwargs)
File "condaclimain.py", line 145, in _main
exit_code = args.func(args, p)
File "condaclimain_originate.py", line 68, in carry out
inshigh(args, parser, 'originate')
File "condacliinshigh.py", line 422, in inshigh
lift CondaSystemExit('Exiting', e)
File "C:Program FilesAnaconda3libcontextlib.py", line 77, in __exit__
self.gen.throw(type, cherish, trackback)
File "condaclicommon.py", line 573, in json_better_bars
produce
File "condacliinshigh.py", line 420, in inshigh
lift CondaRuntimeError('RuntimeError: %s' % e)
conda.exceptions.CondaRuntimeError: Runtime error: RuntimeError: Runtime error: Could not uncover 'C:\Program Files\Anaconda3\pkgs\icu-57.1-vc9_0.tar.bz2.part' for writing (('Connection broken: OSError("(10054, 'WSAECONNRESET')",)', OSError("(10054, 'WSAECONNRESET')",))).
Feel free to email me with the order-of-execution for this one (plmitigate comprehfinish that I didn’t actuassociate annoy to
figure out the answer myself… that’s the whole point of this article).
Draprosperg on the code
Back to the innovative bewilder, so that we can converse the subject without accidenhighy frying your brains. What is it
reassociate that originates this unnecessarily challenging? A first observation is that it is a bewilder at all. That is: the stacktrack
isn’t lhelp out on screen in the order of execution. This is a problem because it forces you to menhighy reset up the
lines to figure out what happened.
This, in turn, happens because of the chaining of exceptions: when an exception is liftd during the handling of another
exception, the stacktrack is split into two parts. The first part shows the innovative exception, and the second part shows
the novel exception.
Before trying to get a better textual recurrentation, let’s do some draprosperg on our code, to get a better mental model
of what’s going on:
In this image, the two stacktracks of our chained exception are both recurrented.
The red arrows correact to the first half, the OriginalException.
The blue arrows correact to the second half, the AnotherException
If we map this back to the stacktrack of the innovative bewilder, hopebrimmingy we can finassociate reassociate wrap our heads around the
answer. The flow of the execution was as chases:
We commence with the red line, at B, line 20. There is a call to show_someskinnyg(), which is excuted until…
the tip of the red line, A, line 15, lifts an exception. Thcimpolite the magic of
try/except we then achieve…
the commence of the 2nd blue arrow, C, line 17, where lift_another_exception() is called, which is carry outd
until…
the tip of that blue line, D, line 10 lifts an exception
Note for the beuntamederd: the arrows do not nasty “the execution jumped from the commence of the arrow to the tip”. They nasty:
“if you chase the arrows in the reverse straightforwardion, you’ll go one level up the call-stack”. Actual execution has gone
for the commence of the arrow to a function surrounding the tip, and then step-rational to the tip until at that point an
exception was liftd.
Frames gone missing
With an eye on this picture, another skinnyg may also become more apparent: the stacktrack, as currented by Python, is
inend. Of the red arrow, only the tip, “A” is shown. The commence of the red arrow, “B”, is missing
from the stacktrack.
As far as I can increate, this is the result of an intentional decision by the Python broadeners: they have choosed to
rank succinctness. Since the line is also current in the second stacktrack (for AnotherException), it would be
redundant to show it twice.
Arguably, removing the redundant parts does originate it easier to see, for all but the last exception in the chain, which
sketchs are distinct to that exception. In our example: (“A”) is the only part of the OriginalException
stacktrack that is distinct to that exception.
However, as we’ve seen, it also originates it much challenginger to piece together the brimming story. In particular: the combination of
stacktracks cannot be read top-to-bottom anymore. The very first skinnyg you see is already in the middle of the execution
of the program.
By the way, I skinnyk that Python’s definite wording originates this even more agonizing:
During handling of the above exception, another exception occurred
To me, this one line increates a chronoreasonable story: first the “above exception” happened, and then the “another
exception”. That in turn gently nudges you into reading the stacktracks from top to bottom.
Is Python distinct in this?
I’ve checked a scant other languages and haven’t yet discovered Python’s decision elsewhere. Java, for example, fair shows the brimming
stacktrack for each exception. I can’t reassociate increate whether the selection to prune was pondered and then choosed aobtainst,
or whether it was fair never pondered. Wantipathyver the case, it’s evident that the decision to prune has a transport inant
impact on the readability of the stacktrack.
The missing connect
So, should we fair insert the duplicated part of the trackback back into each exception and call it a day?
Not quite: even in that case, the actual connect between the two stacktracks would still be proposeed, rather than unambiguous.
Let me fracture out the grey pencil in my progressd detailed tool to exhibit:
The grey line is the missing connect, the relationship between the two stacktracks. We can skinnyk about it in multiple
equivalent ways:
an arrow from the try block (definiteassociate: the line of which triggered the first exception) to the except block
(definiteassociate: the line of the which triggered the second exception)
an arrow connecting the respecive first distinct sketchs of the 2 stacktracks
You might even say it’s this arrow that recurrents the “chaining” of the exceptions. It’s the skinnyg that recurrented by
the text “During handling of the above exception, another exception occurred”
If you would have the verbose/redundant version of the stacktrack, you can do a common-prerepair analysis to discover the
first distinct sketchs, e.g. visuassociate or literassociate by pasting both halves into vimdiff. That’s a UX-nightmare, but at
least the adviseation is useable.
In the case of the pruned stacktrack, half the adviseation is readily useable on-screen: since the innovative exception
is currented pruned up to the the “outermost in try” sketch, fair commence reading at the top to discover it. However, the other
half of the adviseation is missing: the “outermost in regulater” sketch is not shown. In fact, it cannot even be inferred
from the stacktrack, not even when referencing the source code.
Bugsink’s get
I didn’t discover the uncovering bewilder in a bewilder-book, nor was I born with an indynamic obsession with stacktracks.
My personal interest in Stacktracks is heavily swayd by my labor on Bugsink, a tool for error tracking with a key role for stacktracks.
I have tried to originate the flow of execution for chained exceptions more evident by inserting non-obtrusive annotations to the
right hand side of the stacktrack. I’ve used the chaseing annotations:
→ commence is used to show where the regulate-flow commences
try… is used to annotate the “outermost in try” sketch
lift Exception, possibly annotated with (regulated), is used to annotate the points of raising.
Unblessedly, the “outermost in regulater” sketch, i.e. the point in the stacktrack inside the handling block, where the
second exception commences, cannot be annotated in an automated way. Still, I skinnyk it sees more comprehfinishable than the
default:
If you enjoy your stacktracks enjoy that, inshigh Bugsink, if even in local broadenment only.
Inshighing and then reading might fair be speedyer than wrapping your head around an un-annotated version of the same exception.
For the rest of you… perhaps we should try re-uncovering this converseion at the language level?