Everyone,Given the resounding silence I'm inclined to submit this to the Steering Council. While I'm technically a co-author, Irit has done almost all the work, and she's done a great job. If there are no further issues I'll send this SC-wards on Monday.
--GuidoOn Sat, Mar 20, 2021 at 10:05 AM Irit Katriel <iritk...@googlemail.com> wrote:We would like to present for feedback a new version of PEP 654, which incorporates the feedback we received in the discussions so far:The reference implementation has also been updated along with the PEP.The changes we made since the first post are:1. Instead of ExceptionGroup(BaseException), we will have two new builtin types: BaseExceptionGroup(BaseException) and ExceptionGroup(BaseExceptionGroup, Exception).This is so that "except Exception" catches ExceptionGroups (but not BaseExceptionGroups). BaseExceptionGroup.__new__ inspects the wrapped exceptions, and if they are all Exception subclasses, it creates an ExceptionGroup instead of a BaseExceptionGroup.2. The exception group classes are not final - they can be subclassed and split()/subgroup() work correctly if the subclass overrides the derive() instance method as described here: https://www.python.org/dev/peps/pep-0654/#subclassing-exception-groups3. We had some good suggestions on formatting exception groups, which we have implemented as you can see in the output shown for the examples in the PEP.4. We expanded the section on handling Exception Groups, to show how subgroup can be used (with side effects) to do something for each leaf exception, and how to iterate correctly when the tracebacks of leaf exceptions are needed: https://www.python.org/dev/peps/pep-0654/#handling-exception-groups5. We expanded the sections on rationale and backwards compatibility to explain our premise and expectations regarding how exception groups will be used and how the transition to using them will be managed.6. We added several items to the rejected ideas section.We did not receive any comments (or make any changes) to the proposed semantics of except*, hopefully this is because everyone thought they are sensible.Irit, Yury and Guido
----Guido van Rossum (python.org/~guido)Pronouns: he/him (why is my pronoun here?)
_______________________________________________
Python-Dev mailing list -- pytho...@python.org
To unsubscribe send an email to python-d...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Hello,
Hi Paul,IIUC, you are saying that exception group should not be a builtin type because it is (1) complex (2) special-purposed. Instead, you propose that we make exception handling pluggable.
It definitely feels like a lot of effort went into devising and
polishing ExceptionGroup's and except*, thanks. But I'm not sure if you
gentlemen come up with the "ultimate" way to deal with multiple errors,
which deserves being codified on the language level (vs be added as
pluggable means indeed).
> > > def chained(e, excs):
> > > while e:
> > > if isinstance(e, excs):
> > > return e
> > > e = e.__cause__ # Simplified, should consider __context__
> > > too
> > >
> > > try:
> > > tempfile.TemporaryDirectory(...)
> > > except *chained(OSError) as e:
> > > print(e)
>
> What if the user code raised an OSError too, so now that exception
> group has more than one?
I don't see how that's different from PEP654's ExceptionGroup's case.
It's just in ExceptionGroup, there would be 2 (unrelated?) OSError's,
while using normal chaining rules, there's at least context info which
is preserved.
> I am interested to know (1) what limitations you see in the
> ExceptionGroup type we are proposing (in what sense is it
> special-purposed?
Just as a specific example, what if my application indeed needs
ExceptionGroup's (and not just more flexible matching of existing
exception chains), but I want it to be a bit simpler: a) to have a flat
structure of contained exceptions; b) want to actually emphasize the
users that they should not rely on the ordering, so want it to be a set.
My understanding is that ExceptionGroup is carefully designed to allow
subclassing it and override behavior, and the above should be possible
by subclassing.
But usually, a simpler base class is subclassed to have
more advanced/complex behavior. So again, current ExceptionGroup feels
kind of specialized with its careful attention to hierarchy and
multi-story "3D" tracebacks.
While we're talking about compelling use cases, does anyone have an
actual, concrete use case for the proposed "except *" feature that's
strong enough to justify new syntax?
I'm fine with having ExceptionGroup as a built-in type. I'm not fine
with adding new syntax that will apparently be used only in rare
circumstances.
Can code that's aware of the possibility of getting an ExceptionGroup
not simply catch it as a normal exception and then pick it apart? Do
we really need a whole new piece of machinery for this?
_______________________________________________
Python-Dev mailing list -- pytho...@python.org
To unsubscribe send an email to python-d...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/pytho...@python.org/message/MQ2UCSQ2ZC4FIGT7KSVI6BJA4FCXSOCL/
Code of Conduct: http://python.org/psf/codeofconduct/
- Recording pre-empted exceptions: This is another type of metadata that would be useful to print along with the traceback. It's non-obvious and a bit hard to explain, but multiple trio users have complained about this, so I assume it will bite asyncio users too as soon as TaskGroups are added. The situation is, you have a parent task P and two child tasks C1 and C2:
P
/ \
C1 C2
C1 terminates with an unhandled exception E1, so in order to continue unwinding, the nursery/taskgroup in P cancels C2. But, C2 was itself in the middle of unwinding another, different exception E2 (so e.g. the cancellation arrived during a `finally` block). E2 gets replaced with a `Cancelled` exception whose __context__=E2, and that exception unwinds out of C2 and the nursery/taskgroup in P catches the `Cancelled` and discards it, then re-raises E1 so it can continue unwinding.
The problem here is that E2 gets "lost" -- there's no record of it in the final output. Basically E1 replaced it. And that can be bad: for example, if the two children are interacting with each other, then E2 might be the actual error that broke the program, and E1 is some exception complaining that the connection to C2 was lost. If you have two exceptions that are triggered from the same underlying event, it's a race which one survives.
OK, better late than never... here's a much-delayed review of the PEP. Thank you Irit and Guido for carrying this forward while I've been AWOL! It's fantastic to see my old design sketches turned into something like, actually real.
== Overall feelings ==
Honestly, I have somewhat mixed feelings ExceptionGroups. I don't see any way around adding ExceptionGroups in some form, because it's just a fact of life that in a concurrent program, multiple things can go wrong at once, and we want Python to be usable for writing concurrent programs. Right now the state of the art is "exceptions in background threads/tasks get dropped on the floor", and almost anything is better than that. The current PEP is definitely better than that. But at the same time, there are a lot of compromises needed to retrofit this onto Python's existing system, and the current proposal feels like a bunch of awkward hacks with hacks on top. That's largely my fault for not thinking of something better, and maybe there is nothing better. But I still wish we could come up with something more elegant, and I do see why this proposal has made people uncomfortable.
For example:
- I'm uncomfortable with how in some contexts we treat EG's as placeholders for the contained exceptions, and other places we treat them like a single first-class exceptions. (Witness all the feedback about "why not just catch the ExceptionGroup and handle it by hand?", and imagine writing the docs explaining all the situations where that is or isn't a good idea and the pitfalls involved...) If we could somehow pick one and stick to it then I think that would make it easier for users to grasp. (My gut feeling is that making them pure containers is better, which to me is why it makes sense for them to be @final and why I keep hoping we can figure out some better way for plain 'except' and EGs to interact.)
- If a function wants to start using concurrency internally, then now *all* its exceptions have to get wrapped in EGs and callers have to change *all* their exception handling code to use except* or similar. You would think this was an internal implementation detail that the caller shouldn't have to care about, but instead it forces a major change on the function's public API. And this is because regular 'except' can't do anything useful with EGs.
- We have a special-case hack to keep 'except Exception' working, but it has tricky edge cases (Exceptions can still sneak past if they're paired up with a BaseException), and it really is specific to 'except Exception'; it doesn't work for any other 'except SomeError' code. This smells funny.
Anyway, that's just abstract context to give an idea where I'm coming from. Maybe we just have to accept these trade-offs, but if anyone has any ideas, speak up...
== Most important comment ==
Flat ExceptionGroups: there were two basic design approaches we discussed last year, which I'll call "flat" vs "nested". The current PEP uses the nested design, where ExceptionGroups form a tree, and traceback information is distributed in pieces over this tree. This is the source of a lot of the complexity in the current PEP: for example, it's why EG's don't have one obvious iteration semantics, and it's why once an exception is wrapped in an EG, it can never be unwrapped again (because it would lose traceback information).
The idea of the "flat" design is to instead store all the traceback info directly on the leaf exceptions, so the EG itself can be just a pure container holding a list of exceptions, that's it, with no nesting. The downside is that it requires changes to the interpreter's code for updating __traceback__ attributes, which is currently hard-coded to only update one __traceback__ at a time.
For a third-party library like Trio, changing the interpreter is obviously impossible, so we never considered it seriously. But in a PEP, changing the interpreter is possible. And now I'm worried that we ruled out a better solution early on for reasons that no longer apply. The more I think about it, the more I suspect that flat EGs would end up being substantially simpler all around? So I think we should at least think through what that would look like (and Irit, I'd love your thoughts here now that you're the expert on the CPython details!), and document an explicit decision one way or another. (Maybe we should do a call or something to go over the details? I'm trying to keep this email from ballooning out of control...)
== Smaller points ==
- In my original proposal, EGs didn't just hold a list of exceptions, but also a list of "origins" for each exception. The idea being that if, say, you attempt to connect to a host with an IPv4 address and an IPv6 address, and they raised two different OSErrors that got bundled together into one EG, then it would be nice to know which OSError came from which attempt. Or in asyncio/trio it would be nice if tracebacks could show which task each exception came from. It seems like this got dropped at some point?
On further consideration, I think this might be better handled as a special kind of traceback entry that we can attach to each exception, that just holds some arbitrary text that's inserted into the traceback at the appropriate place? But either way, I think it would be good to be able to attach this kind of information to tracebacks somehow.
- Recording pre-empted exceptions: This is another type of metadata that would be useful to print along with the traceback. It's non-obvious and a bit hard to explain, but multiple trio users have complained about this, so I assume it will bite asyncio users too as soon as TaskGroups are added. The situation is, you have a parent task P and two child tasks C1 and C2:
P
/ \
C1 C2
C1 terminates with an unhandled exception E1, so in order to continue unwinding, the nursery/taskgroup in P cancels C2. But, C2 was itself in the middle of unwinding another, different exception E2 (so e.g. the cancellation arrived during a `finally` block). E2 gets replaced with a `Cancelled` exception whose __context__=E2, and that exception unwinds out of C2 and the nursery/taskgroup in P catches the `Cancelled` and discards it, then re-raises E1 so it can continue unwinding.
The problem here is that E2 gets "lost" -- there's no record of it in the final output. Basically E1 replaced it. And that can be bad: for example, if the two children are interacting with each other, then E2 might be the actual error that broke the program, and E1 is some exception complaining that the connection to C2 was lost. If you have two exceptions that are triggered from the same underlying event, it's a race which one survives.
This is conceptually similar to the way an exception in an 'except' block used to cause exceptions to be lost, so we added __context__ to avoid that. And just like for __context__, it would be nice if we could attach some info to E1 recording that E2 had happened and then got preempted. But I don't see how we can reuse __context__ itself for this, because it's a somewhat different relationship: __context__ means that an exception happened in the handler for another exception, while in this case you might have multiple preempted exceptions, and they're associated with particular points in the stack trace where the preemption occurred.
This is a complex issue and maybe we should call it out-of-scope for the first version of ExceptionGroups. But I mention it because it's a second place where adding some extra annotations to the traceback info would be useful, and maybe we can keep it simple by adding some minimal hooks in the core traceback machinery and let libraries like trio/asyncio handle the complicated parts?
- There are a number of places where the Python VM itself catches exceptions and has hard-coded handling for certain exception types. For example:
- Unhandled exceptions that reach the top of the main thread generally cause a traceback to be printed, but if the exception is SystemExit then the interpreter instead exits silently with status exc.args[0].
- 'for' loops call iter.__next__, and catch StopIteration while allowing other exceptions to escape.
- Generators catch StopIteration from their bodies and replace it with RuntimeError (PEP 479)
With this PEP, it's now possible for the main thread to terminate with ExceptionGroup(SystemExit), __next__ to raise ExceptionGroup(StopIteration), a generator to raise ExceptionGroup(StopIteration), either alone or mixed with other exceptions. How should the VM handle these new cases? Should they be using except* or except?
I don't think there's an obvious answer here, and possibly the answer is just "don't do that". But I feel like the PEP should say what the language semantics are in these cases, one way or another.
- traceback module API changes: The PEP notes that traceback.print_tb and traceback.print_exception will be updated to handle ExceptionGroups. The traceback module also has some special data structures for representing "pre-processed" stack traces, via the traceback.StackSummary type. This is used to capture tracebacks in a structured way but without holding onto the full frame objects. Maybe this API should also be extended somehow so it can also represent traceback trees?
This point reminded me again of this issue in the tracker ("Problems with recursive automatic exception chaining" from 2013): https://bugs.python.org/issue18861I'm not sure if it's exactly the same, but you can see that a couple of the later comments there talk about "exception trees" and other types of annotations.If that issue were addressed after ExceptionGroups were introduced, does that mean there would then be two types of exception-related trees layered over each other (e.g. groups of trees, trees of groups, etc)? It makes me wonder if there's a more general tree structure that could accommodate both use cases...--Chris
The problem is that most of the time, even if you're using concurrency
internally so multiple things *could* go wrong at once, only one thing
actually *does* go wrong. So it's unfortunate if some existing code is
prepared for a specific exception that it knows can be raised, that
exact exception is raised... and the existing code fails to catch it
because now it's wrapped in an EG.
> It is easy enough to write a denormalize() function in traceback.py that constructs this from the current EG structure, if you need it (use the leaf_generator from the PEP). I'm not sure I see why we should trouble the interpreter with this.
In the current design, once an exception is wrapped in an EG, then it
can never be unwrapped, because its traceback information is spread
across the individual exception + the EG tree around it. This is
confusing to users ("how do I check errno?"), and makes the design
more complicated (the need for topology-preserving .split(), the
inability to define a sensible EG.__iter__, ...). The advantage of
making the denormalized form the native form is that now the leaf
exceptions would be self-contained objects like they are now, so you
don't need EG nesting at all, and users can write intuitive code like:
except OSError as *excs:
remainder = [exc for exc in excs if exc.errno != ...]
if remainder:
raise ExceptionGroup(remainder)
> For display purposes, it is probably nicer to look at a normalized traceback where common parts are not repeated.
Yeah, I agree; display code would want to re-normalize before
printing. But now it's only the display code that needs to care about
figuring out shared parts of the traceback, rather than something that
has to be maintained as an invariant everywhere.
>
> It sounds like you want some way to enrich exceptions. This should be optional (we wouldn't want EG to require additional metadata for exceptions) so yeah, I agree it should sit on the leaf exception and not on the group. In that sense it's orthogonal to this PEP.
Well, the extra metadata would specifically be at "join" points in the
traceback, which are a thing that this PEP is creating :-). And it's
useful for every user of EGs, since by definition, an EG is
multiplexing exceptions from multiple sources, so it's nice to tell
the user which sources those are.
That said, you're right, if we want to handle this by defining a new
kind of traceback entry that code like Trio/asyncio/hypothesis can
manually attach to exceptions, then that could be written as a
separate-but-complementary PEP. In my original design, instead of
defining a new kind of traceback entry, I was storing this on the EG
itself, so that's why I was thinking about it needing to be part of
this PEP.
_______________________________________________
Python-Dev mailing list -- pytho...@python.org
To unsubscribe send an email to python-d...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Saying that you have to make a new API every time you start using
concurrency inside a function is extremely restrictive.
On Tue, Apr 20, 2021 at 3:15 AM Irit Katriel <iritk...@googlemail.com> wrote:
> On Tue, Apr 20, 2021 at 2:48 AM Nathaniel Smith <n...@pobox.com> wrote:
>>
>>
>> The problem is that most of the time, even if you're using concurrency
>> internally so multiple things *could* go wrong at once, only one thing
>> actually *does* go wrong. So it's unfortunate if some existing code is
>> prepared for a specific exception that it knows can be raised, that
>> exact exception is raised... and the existing code fails to catch it
>> because now it's wrapped in an EG.
>
> Yes, this was discussed at length on this list. Raising an exception group is an API-breaking change. If a function starts raising exception groups its callers need to be prepared for that. Realistically we think exception groups will be raised by new APIs. We tried and were unable to define exception group semantics for except that would be reasonable and backwards compatible. That's why we added except*.
Sure. This was in my list of reasons why the backwards compatibility
tradeoffs are forcing us into awkward compromises. I only elaborated
on it b/c in your last email you said you didn't understand why this
was a problem :-). And except* is definitely useful. But I think there
are options for 'except' that haven't been considered fully.
Saying that you have to make a new API every time you start using
concurrency inside a function is extremely restrictive. The whole
point of structured concurrency (and structured programming in
general) is that function callers don't need to know about the control
flow decisions inside a function. So right now, the EG proposal is
like saying that if you every have a function that doesn't contain a
'for' loop, and then you want to add a 'for' loop, then you have to
define a whole new API instead of modifying the existing one.
I absolutely get why the proposal looks like that. I'm just making the
point that we should make sure we've exhausted all our options before
settling for that as a compromise.
Or here's another example. Earlier you suggested:
> If you are using concurrency internally and don't want to raise EGs externally, then surely you will catch EGs, select one of the exceptions to raise and throw away all the others
But with nested EGs, this is difficult, because you *can't* just pull
out one of the exceptions to raise. The leaf exceptions aren't
standalone objects; you need some obscure traceback manipulation to do
this. I guarantee that users will get this wrong, even if we provide
the tools, because the explanation about when and why you need the
tools is complicated and people won't internalize it.
With flat EGs, this is trivial: it's just `raise
ExceptionGroup[the_selected_exception_index]`.
> 2. Display code will need to normalize the traceback, which is much more complicated than denormalizing because you need to discover the shared parts.
>
> Am I missing something?
I think re-normalizing is very cheap and simple? It's just a common
prefix problem. Given a tree-in-progress and a traceback, walk down
each of them in parallel until you run out of matching entries, then
insert a branch node and append the rest of the traceback.
>> >
>> > It sounds like you want some way to enrich exceptions. This should be optional (we wouldn't want EG to require additional metadata for exceptions) so yeah, I agree it should sit on the leaf exception and not on the group. In that sense it's orthogonal to this PEP.
>>
>> Well, the extra metadata would specifically be at "join" points in the
>> traceback, which are a thing that this PEP is creating :-). And it's
>> useful for every user of EGs, since by definition, an EG is
>> multiplexing exceptions from multiple sources, so it's nice to tell
>> the user which sources those are.
>>
>> That said, you're right, if we want to handle this by defining a new
>> kind of traceback entry that code like Trio/asyncio/hypothesis can
>> manually attach to exceptions, then that could be written as a
>> separate-but-complementary PEP. In my original design, instead of
>> defining a new kind of traceback entry, I was storing this on the EG
>> itself, so that's why I was thinking about it needing to be part of
>> this PEP.
>
>
> You can also create an ExceptionGroup subclass with whatever extra data you want to include.
Unfortunately, that doesn't work either, because this is data that
should be included in traceback displays, similar to __cause__ and
__context__...
On Wed, Apr 21, 2021 at 4:50 PM Guido van Rossum <gu...@python.org> wrote:
> On Wed, Apr 21, 2021 at 3:26 PM Nathaniel Smith <n...@pobox.com> wrote:
>> Sure. This was in my list of reasons why the backwards compatibility
>> tradeoffs are forcing us into awkward compromises. I only elaborated
>> on it b/c in your last email you said you didn't understand why this
>> was a problem :-). And except* is definitely useful. But I think there
>> are options for 'except' that haven't been considered fully.
>
> Do you have any suggestions, or are you just telling us to think harder? Because we've already thought as hard as we could and within all the constraints (backwards compatibility and otherwise) we just couldn't think of a better one.
The main possibility that I don't think we've examined fully is to
make 'except' blocks fire multiple times when there are multiple
exceptions. We ruled it out early b/c it's incompatible with nested
EGs, but if flat EGs are better anyway, then the balance shifts around
and it might land somewhere different. it's a tricky discussion
though, b/c both the current proposal and the alternative have very
complex implications and downsides. So we probably shouldn't get too
distracted by that until after the flat vs nested discussion has
settled down more.
I'm not trying to filibuster here -- I really want some form of EGs to
land.
Message archived at https://mail.python.org/archives/list/pytho...@python.org/message/4BAOL763Y2O2AXLEILYGHSNG2VMZJIN6/
I have now formally filed a final lawsuit against the manager of the python program company, because all of him is also a criminal act, and GNU has EU legal certification, only my key can log in, and the key must be recycled after the death of the holder, and gitlab allows to change It’s the most basic and important crime to log in by people who support the snatching of the key. I have to explain to you that the key is to be registered and authenticated. My girlfriend wants to authenticate me with this key, and my information is also there. The key is authenticated, so I will not log in now, and I have submitted a lawsuit against him and the authority of the key holder to the U.S. Supreme Court and the European Union. I will not log in until there is a judgment or the U.S. Supreme Court allows me. People will be litigated, and the information that has been changed online will be found out, and I have library files, I have all the original materials, please cooperate with me, my key is called the Boss key, all websites of the program, companies, Institutions, banks, third-party platforms, and only my keys can have them, including patents and copyrights.
Message archived at https://mail.python.org/archives/list/pytho...@python.org/message/4BAOL763Y2O2AXLEILYGHSNG2VMZJIN6/
Yes, I got one from the same address today. Thanks for pointing out these are individual peformances: it was annoying when I thought it was spam to the list.
Although Hoi Lam Poon is a real (female) name, it may signify a
generated lampoon.
Jeff Allen
_______________________________________________
Python-Dev mailing list -- pytho...@python.org
To unsubscribe send an email to python-d...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/pytho...@python.org/message/3EA53I3Y72ACEPDVG467NMNTXHRL3NXL/