Skip to content
This repository was archived by the owner on Apr 10, 2022. It is now read-only.

added section on tracebacks #8

Merged
merged 7 commits into from
Jan 20, 2021
Merged

Conversation

iritkatriel
Copy link
Member

Added a section on traceback groups.

I assume there will be a section on exception group API before it, so the reference to splitting operations etc make sense.

We'll need some pictures of trees.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I think about it, the more I believe we should be very careful whenever we're not following Trio's design (either the current MultiError design or the MultiError v2 design from python-trio/trio#611)...

@iritkatriel iritkatriel changed the title added section on TracebackGroups added section on tracebacks Jan 5, 2021
Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am so glad we don't need a separate tree of tracebacks! I think this is an important simplification of the design. (I should have realized it was too complicated when I tried to draw diagrams of how it worked. :-)

I left two nits to the text.

@iritkatriel
Copy link
Member Author

I have a concern re trackbacks though. We said an ExceptionGroup is iterable so list(eg) is a flat list of the contained exceptions. But the trackbacks of those exceptions are partial. Their cause and context are wrong too, they should be the same as those of the topmost eg. It seems that we need an operation to extract an exception with the correct tb, cause, context. The tb part of the eg needs to be copied. Then something around the iteration api, not sure what. Or just document that the metadata is not reconstructed when you take an exception out of a group.

@iritkatriel
Copy link
Member Author

We sort of do have that extract operation already - when we extract an exception in the form of a singleton ExceptionGroup via project() then the group has the correct metadata.

Copy link
Member

@1st1 1st1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Happy we can keep tracebacks simple!

@1st1
Copy link
Member

1st1 commented Jan 20, 2021

Then something around the iteration api, not sure what. Or just document that the metadata is not reconstructed when you take an exception out of a group.

IMO just documenting that would be OK. We can also add some helpers to the traceback module to flatten ExceptionGroup objects more carefully.

Co-authored-by: Yury Selivanov <yury@edgedb.com>
@iritkatriel
Copy link
Member Author

iritkatriel commented Jan 20, 2021

Then something around the iteration api, not sure what. Or just document that the metadata is not reconstructed when you take an exception out of a group.

IMO just documenting that would be OK. We can also add some helpers to the traceback module to flatten ExceptionGroup objects more carefully.

Maybe we don't need ExceptionGroup to be iterable at all. The use case of selecting OSError by code (https://github.com/python/exceptiongroups/blob/master/except_star.md#raising-exceptiongroups-manually) can be implemented with something like eg.project(lambda e: isinstnace(e, OSError) and e.errno != errno.EPIPE).

Is there another use case?

How about we provide this project method along with something that squashes singleton EGs, so if we have EG("1", SyntaxError(1), EG("2", ValueError(2))) then the squashed version is EG("1", SyntaxError(1), ValueError(2)) where the traceback of the EG("2") that was squashed away was concatenated as the prefix of the ValueError is was wrapping. These two operations are sound and will probably be enough to do anything someone needs to do with ExceptionGroups.

Note that the experimental implementation doesn't have the iterate operation, only a flatten helper method in the unit tests (it's not needed other than for the tests).

@iritkatriel iritkatriel merged commit 0c9e8ac into python:master Jan 20, 2021
@1st1
Copy link
Member

1st1 commented Jan 20, 2021

Can you add a link to the experimental implementation to the README?

@gvanrossum
Copy link
Member

Maybe we don't need ExceptionGroup to be iterable at all.

Agreed, I have been wondering this for a while. Then we wouldn't have to worry about whether an EG with no exceptions is truthy or falsy or a forbidden edge case.

You can still access the bare subexceptions using eg.excs (hm, maybe a less abbrev name here?).

Related, I also wonder whether the constructor should take a list of exceptions instead of being varargs. I.e., ExceptionGroup("msg", [e1, e2, e3]) instead of ExceptionGroup("msg", e1, e2, e3). Usually the list of subexceptions is already available as a list anyway.

@1st1
Copy link
Member

1st1 commented Jan 21, 2021

Related, I also wonder whether the constructor should take a list of exceptions instead of being varargs. I.e.,

+1 for the list option. I vaguely recall we already discussed this somewhere else, btw.

@iritkatriel
Copy link
Member Author

I've added the link in readme.

Re list - yes, it was a list initially, then we moved to varargs (before we added the msg arg). Do we want both varargs and sequence? Or just sequence? Or just list?

@1st1
Copy link
Member

1st1 commented Jan 21, 2021

I've added the link in readme.

Thanks! I'll play with it on the weekend.

Re list - yes, it was a list initially, then we moved to varargs (before we added the msg arg). Do we want both varargs and sequence? Or just sequence? Or just list?

My preference would be to go with an iterable/sequence (but no varargs).

@iritkatriel
Copy link
Member Author

I'm thinking about how to implement raise inside an except* block and came across this question:

At the moment, if we do this:

try:
    raise ValueError(12)
except *TypeError as e:
    raise

we get

Traceback (most recent call last):
  File "C:\Users\User\src\cpython\tmp.py", line 7, in <module>
    raise ValueError(12)
ValueError: 12

which is what you would expect when an exception is not matched.

But if we do this:

try:
    raise ValueError(12)
except *ValueError as e:
    raise

we get:

Running Release|x64 interpreter...
Traceback (most recent call last):
  File "C:\Users\User\src\cpython\tmp.py", line 7, in <module>
    raise ValueError(12)
ExceptionGroup
This exception has 1 sub-exceptions:
 ----------------- 1/1 -----------------
 Traceback (most recent call last):
   File "C:\Users\User\src\cpython\tmp.py", line 7, in <module>
     raise ValueError(12)
 ValueError: 12
 ----------------- end of 1 -----------------

This is because the except* wraps the ValueError in an exception group, and that gets matched and reraised.
Is this what we want?

@gvanrossum
Copy link
Member

gvanrossum commented Jan 22, 2021

I think it's acceptable. If you were to simplify the exception if the EG has exactly one subexception, you'd have the converse inconsistency, and I don't think adding more hidden state to track this case is a great idea either.

Bare raise already appears to use hidden state to distinguish itself from raise e, and that hidden state is already more complex that you'd think because the bare raise could occur inside a function called at that point too. I don't want to complicate things further.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants