This message is longer than I had anticipated.

To aid comprehension, I'm:

- accepting that .split and .subgroup help my "handle some excpetions 
  but not others" situation, barely

- arguing for ExceptionGroups acting like other containers: truthy if 
  nonempty, falsey if empty; iterable; .subgroup and .split _not_ 
  returning None for an empty subgroup, so that the container-like 
  aspects can be used directly

On 28Feb2021 10:40, Irit Katriel <iritkatr...@googlemail.com> wrote:
>split() and subgroup() take care to preserve the correct metadata on 
>all
>the internal nodes, and if you just use them you only make safe operations.
>This is why I am hesitating to add iteration utilities to the API. Like we
>did, people will naturally try that first, and it's not the safest API.

Wouldn't it be safe if the ExceptionGroup were immutable, as you plan?  
Or have you another hazard in mind?

>We actually have the  OSErrors example in the PEP, just above
>https://www.python.org/dev/peps/pep-0654/#caught-exception-objects:
>
>try:
>    low_level_os_operation()
>except *OSerror as errors:
>    raise errors.subgroup(lambda e: e.errno != errno.EPIPE) from None

Indeed. That basicly addresses my pattern. Along with:

>On Sun, Feb 28, 2021 at 6:30 AM Guido van Rossum <gu...@python.org> 
>wrote:
>> There’s a pattern for what you propose using the split() method and a
>> lambda, or you can keep the exceptions in a list and re-wrap them at the
>> end.

The keep-a-list approach was my fallback, absent a way to push an 
unhandled exception back in some sense.

Guido:
>> We really don’t want users pushing non-exceptions into the list, nor 
>> do we
>> want e.g. KeyboardInterrupt to be added to a (non-Base-) ExceptionGroup.

I was only imagining pushing exceptions from the original group back in.  
Enforcing that would be tedious and confusing though, so I was imagining 
some way of marking specific subexeceptions as handled or not handled.

But I had not understood the subgroup method.

I think my handled/unhandled concerns are (barely) sufficient addressed 
above.  If I wanted to sweep the group for handled exceptions and then 
reraise the unhandled ones in their own ExceptionGroup at the end, that 
seems tractable.

But all that said, being able to iterable the subexceptions seems a 
natural way to manage that:

    unhandled = []
    try:
        .........
    except *OSError as eg:
        for e in eg:
            if an ok exception:
                handle it
            else:
                unhandled.append(e)
    if unhandled:
        raise ExceptionGroup("unhandled", unhandled)

There are some immediate shortcomings above. In particular, I have no 
way of referencing the original ExceptionGroup without surprisingly 
cumbersome:

    try:
        .........
    except ExceptionGroup as eg0:
        unhandled = []
        eg, os_eg = eg0.split(OSError)
        if os_eg:
            for e in os_eg:
                if an ok exception:
                    handle it
                else:
                    unhandled.append(e)
        if eg:
            eg, value_eg = eg.split(ValueError)
            if value_eg:
                for e in value_eg:
                    if some_ValueError_we_understand:
                        handle it
                    else:
                        unhandled.append(e)
        if eg:
            unhandled.append(eg)
        if unhandled:
            raise ExceptionGroup("unhandled", unhandled) from eg0

I have the following concerns with the pattern above:

There's no way to make a _new_ ExceptionGroup with the same __cause__ 
and __context__ and message as the original group: not that I can't 
assign to these, but I'd need to enuerate them; what if the exceptions 
grew new attributes I wanted to replicate?

This cries out for another factory method like .subgroup but which makes 
a new ExceptionGroup from an original group, containing a new sequence 
of exceptions but the same message and coontext. Example:

    unhandled_eg = eg0.with_exceptions(unhandled)

I don't see a documented way to access the group's message.

I'm quite unhappy about .subgroup (and presumably .split) returning None 
when the group is empty. The requires me to have the gratuitous "if eg:" 
and "if value_eg:" if-statements in the example above.

If, instead, ExceptionGroups were like any other container I could just 
test if they were empty:

    if eg:

_and_ even if they were empty, iterate over them. Who cares if the loop 
iterates zero times? The example code would become:

    try:
        .........
    except ExceptionGroup as eg0:
        unhandled = []
        eg, os_eg = eg0.split(OSError)
        for e in os_eg:
            if an ok exception:
                handle it
            else:
                unhandled.append(e)
        eg, value_eg = eg.split(ValueError)
        for e in value_eg:
            if some_ValueError_we_understand:
                handle it
            else:
                unhandled.append(e)
        if eg:
            unhandled.append(eg)
        if unhandled:
            raise eg0.with_exceptions(unhandled)

You'll note that "except*" is not useful in this pattern. However...

If a subgroup had a reference to its parent this gets cleaner again:

    unhandled = []
    eg0 = None
    try:
        .........
    except* OSError as os_eg:
        eg0 = os_eg.__original__  # or __parent__ or something
        for e in os_eg:
            if an ok exception:
                handle it
            else:
    except* ValueError as value_eg:
        eg0 = os_eg.__original__  # or __parent__ or something
        for e in value_eg:
            if some_ValueError_we_understand:
                handle it
            else:
                unhandled.append(e)
    except* Exception as eg:
        eg0 = os_eg.__original__  # or __parent__ or something
        unhandled.extend(eg)
    if unhandled:
        raise eg0.with_exceptions(unhandled)

Except that here I have no way to get "eg0", the original 
ExceptionGroup, for the final raise without the additional .__original__ 
attribute.

Anyway, I'm strongly of the opinion that ExceptionGroups should look 
like containers, be iterable, be truthy/falsey based on empty/nonempty 
and that .split and .subgroup should return empty subgroups instead of 
None.

Cheers,
Cameron Simpson <c...@cskk.id.au>
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/XLJCHKZMFMIHBGVSVMFPRQQG2SZVVHEJ/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to