Hi Sven, 

I like your formatting suggestion, thanks. I will do something like that.

I'm not sure I understand your question. ExceptionGroup is a subclass of Exception (which is a subclass of BaseException). So ExceptionGroup is caught by "except Exception" or "except BaseException". BaseExceptionGroup is a subclass only of BaseException so it is caught by "except BaseException" but not "except Exception". And ExceptionGroup is allowed to wrap only Exceptions while BaseException can wrap Exceptions and and BaseExceptions. Makes sense?

Irit


On Tue, Mar 2, 2021 at 7:25 PM Sven R. Kunze <srkunze@mail.de> wrote:

Just to be on the safe side here, I would want to asked a little bit further.


On 02.03.21 12:22, Irit Katriel wrote:

1) What is the right "except pattern" when ExceptionGroup is introduced for the use cases described above (remote or concurrent python processes)?

 
If you want to catch the whole ExceptionGroup and format its traceback, then you can just do "except ExceptionGroup as eg" and then traceback.print_exception(eg).

The except* syntax is for when you want to handle only certain types of exceptions from the group, selectively.

Just to make sure, I understand this completely.


In order to make it more tangible here is an example from the stdlib:

https://github.com/python/cpython/blob/bf2e7e55d7306b1e2fce7dce767e8df5ff42cf1c/Lib/concurrent/futures/process.py#L215

As you can see, BaseException is caught multiple times as the only except-clause. _sendback_result(...) then used to transfer the return_val/exception back to parent process.


How is the supposed way of handling a raised ExceptionGroup?


a) will the existing code simply work as it?
b) if not what are the changes to this lib specifically as a blueprint example for others


Reading from the other subthread for this PEP, I am not 100% sure now that "except BaseException" will suffice if ExceptionGroup inherits from Exception instead of BaseException because it seems that ExceptionGroup somehow is handled specially. So, why I try to approach this very issue with existing code. Once that is clear, it should be easy for everybody to apply the same pattern to their code. Here is the copy from github:


try:
    r = call_item.fn(*call_item.args, **call_item.kwargs)
except BaseException as e:
    exc = _ExceptionWithTraceback(e, e.__traceback__)
    _sendback_result(result_queue, call_item.work_id, exception=exc)
else:
    _sendback_result(result_queue, call_item.work_id, result=r)
    del r


Maybe even _sendback_result could benefit from using ExceptionGroup itself. You can see there another "except BaseException" in case of an error. But that's maybe a different topic.

 

2) What is the recommended approach of printing the traceback potentially incorporating multiple tracebacks - I couldn't find it in the PEP and tracebacks are a really neat tool for error hunting.


Part of the proposal of the PEP is that we teach the builtin traceback formatting code to display ExceptionGroups with all that information.


As long as there's the possibility to receive the usual (two-line) entry-based list, I guess that is enough for every one to work with it (e.g. hiding unnecessary tb entries).


The reference implementation has this, and the examples in the PEP were produced with it. Some of the examples (the early ones) show exceptions that were never raised so there is no traceback. But if you scroll down to the later examples, you see the output for exceptions with tracebacks, cause, context etc.


Ah I see them now. Thank you. :-)


We didn't put too much effort into making the traceback output pretty, so at the moment it's a draft. If there is an ascii artist out there who has suggestions on improving this, that would be great.


Well, yes. It's not really pretty. I would recommend a tree-based solution such as the following:


Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
ExceptionGroup
|
+---+   Traceback (most recent call last):
|   |     File "<stdin>", line 3, in <module>
|   |   ExceptionGroup: eg
|   |   |
|   |   +---+ ValueError: a
|   |
|   |   During handling of the above exception, another exception occurred:
|   | 
|   |   Traceback (most recent call last):
|   |     File "<stdin>", line 5, in <module>
|   |   KeyError: 'x'
|
|
+---+   Traceback (most recent call last):
|   |     File "<stdin>", line 3, in <module>
|   |   ExceptionGroup: eg
|   |   |
|   |   +---+ TypeError: b


I used a similar pattern for the remote-execution lib and received good user feedback (even requesting color encoding for each layer of the tree (not the text), so people know what belongs together). Besides colors, I used https://en.wikipedia.org/wiki/Box-drawing_character but I guess pipes, dashes and pluses could suffice for CPython.


One other remark from my side here: in the example of the PEP there seem to be missing a space before 'File "<stdin>"'. Comparing outer tbs with inner tbs, you can see that the "F" of "File" is not underneath the a of "Traceback" but underneath the "r" of it.

Regards,
Sven