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

A case for raise* #7

Closed
1st1 opened this issue Oct 27, 2020 · 5 comments
Closed

A case for raise* #7

1st1 opened this issue Oct 27, 2020 · 5 comments

Comments

@1st1
Copy link
Member

1st1 commented Oct 27, 2020

This builds on top of what's defined in here (permalink)


According to the latest spec, using raise e or raise inside an except* clause wouldn't flatten the tree of errors, e.g.:

try:
  raise ExceptionGroup(
    ValueError('a'),
    TypeError('b'),
    ExceptionGroup(
      TypeError('c'),
      KeyError('d')
    )
  )
except *TypeError as e:
  raise  # or "raise e"

# would both terminate with:
#
#  ExceptionGroup(
#    ValueError('a'),
#    TypeError('b'),
#    ExceptionGroup(
#      TypeError('c'),
#      KeyError('d')
#    )
#  )

But when raising an ExceptionGroup() instance manually the tree would be flattened:

try:
  raise ExceptionGroup(
    ValueError('a'),
    TypeError('b'),
    ExceptionGroup(
      TypeError('c'),
      KeyError('d')
    )
  )
except *TypeError as e:
  raise ExceptionGroup(*e)

# would terminate with:
#
#  ExceptionGroup(
#    ValueError('a'),
#    ExceptionGroup(
#      TypeError('b'),
#      TypeError('c'),
#    ),
#    ExceptionGroup(
#      KeyError('d')
#    )
#  )

This makes the case of "I only want to handle one or few exceptions from the group and re-raise all others without flattening them" a bit problematic. But what if we could use raise * syntax for that:

try:
  raise ExceptionGroup(
    ValueError('a'),
    TypeError('b'),
    ExceptionGroup(
      TypeError('c'),
      KeyError('d')
    )
  )
except *TypeError as e:
  er1, *rest = e

  log(er1)  # we'd like to ignore er1, just log it

  raise *rest

# would both terminate with:
#
#  ExceptionGroup(
#    ValueError('a'),
#    ExceptionGroup(
#      TypeError('c'),
#      KeyError('d')
#    )
#  )
@1st1
Copy link
Member Author

1st1 commented Oct 27, 2020

We can even construct a relatively realistic example for this: ignoring all OSError(errno=ESOMETHING) from the group preserving where all OSErrors positions in the group tree.

@gvanrossum
Copy link
Member

gvanrossum commented Oct 27, 2020

Alternatively, we could have a split operation like in Trio's MultiError v2 design, which takes a predicate and returns two MultiError instances with the same substructure. Then if we were to use that same split operation to split off the exceptions matching the except clause, e would have the same structure, and so would er1 and rest (when using that split operation). The merge operation should do the right thing so that plain raise e and bare raise would result in the right thing happening, so you wouldn't need raise *.

The split operation should be recursive, e.g. (untested): UPDATED

class MultiError(BaseException):
    def split(self, pred):
        a = []
        b = []
        for e in self.inner_exceptions:
            if isinstance(e, MultiError):
                a1, b1 = e.split(pred)
                if a1:
                    a.append(a1)
                if b1:
                    b.append(b1)
            elif pred(e):
                a.append(e)
            else:
                b.append(e)
        return group(a), group(b)

where

def group(excs):
    if excs:
        return ExceptionGroup(excs)
    else:
        return None

@iritkatriel
Copy link
Member

ISTM that we don't really have a choice in the matter - the split operation has to preserve the nested structure because otherwise the tracebacks are wrong. Exceptions that belong to the same ExceptionGroup share the traceback of the group, and then they split. If we offer a flatten operation for Exception groups, then it needs to also set all the tracebacks to None.

@iritkatriel
Copy link
Member

iritkatriel commented Jan 3, 2021

Related to the above - I think this example from the current document is incorrect. The KeyError should not be merged into the EG of the TypeError, because it doesn't have that EG's traceback, and it as the matched part of the EG as its context.

try:
  raise ExceptionGroup(ValueError('a'), TypeError('b'))
except *ValueError:
  raise KeyError('x')

# would result in:
#
#   ExceptionGroup(
#     KeyError('x'),
#     TypeError('b')
#   )

@iritkatriel
Copy link
Member

I'm closing this because I think we settled on preserving the structure of an EG throughout, and we don't provide any iteration/flattening apis for now (we can reconsider when a use case for them turns up).

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

No branches or pull requests

3 participants