A Stacktrace Puzzle

(bugsink.com)

13 points | by thunderbong 8 months ago ago

14 comments

  • o11c 8 months ago

    Am I the only one who finds Python's "forwards" backtraces confusing compared to every other language I've used? I can't help but think at least part of this confusion is because of that.

    That said:

    * Would printing the trace as a tree actually clarify anything?

    * Good code almost always minimizes the amount of code in an exception handler.

    * `raise_exception()` is generally bad practice; consider `raise get_exception()` if you need something complicated (among others, this allows choice of `from`)

    * `raise ... from None` is useful since it's often the most recent stacktrace that's actually relevant.

    * `raise ... from e` likely implies that it is the older stacktrace that's most relevant; ideally this would just be a single annotation on a particular frame "exception was replaced here".

    • silenced_trope 8 months ago

      yes - I'm currently at a new job where I work on a python back-end, but there's also java/kotlin microservices, and I do front-end web with typescript or mobile with java/kotlin

      python is the only one doing this weird crap

    • vanschelven 8 months ago

      You're probably not the only one, but as a Python developer of 20 years I can assure you that the confusion _for me_ is not caused by the order of the Stacktrace itself, but specifically by the way chained exceptions are shown.

      `raise_exception()` in the example is just to ensure a trivial example which still has a stacktrace in the handler (this is not uncommon in reality, though in reality the called function is not typically called with the goal of triggering yet another example)

    • Sohcahtoa82 8 months ago

      > * Would printing the trace as a tree actually clarify anything?

      Could you give an example of what this tree would look like? A stack is entirely linear.

      I've never had a problem with Python's stack traces. Certainly better than Java, where the paradigms rely on a ridiculous amount of introspection, reflection, and abstraction, so your stack traces end up being a hundred lines long, with half of them being ".invoke()".

    • EdwardDiego 8 months ago

      TIL `raise from` especially the `raise from None`, yeesh

  • iamcreasy 8 months ago

    Interesting post. Thanks you for taking the time to write it.

    In the long stack trace example, the first two traces start at the same line 277 which, I think, solves part of the problem you've posed. I need to look at the source code for the rest of the stack trace.

       Traceback (most recent call last):
         File "requests\packages\urllib3\contrib\pyopenssl.py", line 277, in recv_into
           return self.connection.recv_into(*args, \*kwargs)
         File "OpenSSL\SSL.py", line 1335, in recv_into
        self._raise_ssl_error(self._ssl, result)
         File "OpenSSL\SSL.py", line 1149, in _raise_ssl_error
        raise WantReadError()
       OpenSSL.SSL.WantReadError
    
    
       During handling of the above exception, another exception occurred:
    
       Traceback (most recent call last):
         File "requests\packages\urllib3\contrib\pyopenssl.py", line 277, in recv_into
        return self.connection.recv_into(*args, \*kwargs)
         File "OpenSSL\SSL.py", line 1335, in recv_into
        self._raise_ssl_error(self._ssl, result)
         File "OpenSSL\SSL.py", line 1166, in _raise_ssl_error
        raise SysCallError(errno, errorcode.get(errno))
       OpenSSL.SSL.SysCallError: (10054, 'WSAECONNRESET')*
  • gklitz 8 months ago

    Maybe I’m confused about why OP is confused, but doesn’t this all just boil down to the fact that the second exception happens during the first exception so naturally the co text of the second exception contains both things “before” and after the first exception?

    It feels like OP is trying to infer something aboub the source code structure which isn’t to be expected. The second “simple example” is only shown in the same order in stacktrace and code because OP ordered the functions by execution order, but that’s not a given for an actual code base, so you’ll always want to see the context of the exception thrown, and if the exception happened during handling another then you’ll see the full context.

    What’s the supposed puzzle here?

    • vanschelven 8 months ago

      > doesn’t this all just boil down to the fact that the second exception happens during the first exception so naturally the co text of the second exception contains both things “before” and after the first exception?

      My problem is that information is removed from the stacktrace of the first exception, and that this information cannot be constructed by looking at the stacktrace. This makes it so that the first exception's "life story" begins in the middle.

      > because OP ordered the functions by execution order

      That's coincidental.

      I'm arguing that, in the simple example, the execution order can be inferred from the stacktrace alone. Simply by reading top to bottom. This is not true for the first example.

  • TrianguloY 8 months ago

    Wait...why is the OriginalException shown? The try catch already handled it, and the second exception doesn't "use" it and instead create a new one...it shouldn't be shown.

    Is python automatically chaining the exceptions itself in a non-explicit manner?

    Edit: found the PEP: https://peps.python.org/pep-3134/

    • vanschelven 8 months ago

      Yes, the article doesn't mention this but indeed, in Python you can explicitly chain exceptions, which changes the message into "The above exception was the direct cause of the following exception:"

      You can also disable this behavior using the `from None` syntax.

      https://docs.python.org/3/tutorial/errors.html#exception-cha...

    • Sohcahtoa82 8 months ago

      Exception chaining is a good thing.

      If your exception handler throws another exception, then knowing what caused the original exception is really good to know and greatly helps debugging, especially if your code is calling the handling code incorrectly. Without chaining, the root cause of the second exception would be hidden.

      If you find the chained exception output confusing, that's literally a skill issue. It's giving you invaluable information. Learn what it's telling you and you can fix bugs far more easily.

  • lqstuart 8 months ago

    I used to be one of those kids that hated on Java for being boring, until I spent six years doing nothing but deep learning in Python and C++. I miss you, Gradle…

  • playingalong 8 months ago

    It's from April and he didn't get any response in either SO or the mailing list?

    • vanschelven 8 months ago

      Indeed, the SO / mailing list posts went unanswered and I still had this on my list of "interesting ideas" so I did another post on it. This time with more responses (though still no "how to actually fix the problem" ones)