Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undocumented behavior of predicates in ExceptionGroup methods #99563

Closed
sobolevn opened this issue Nov 17, 2022 · 4 comments
Closed

Undocumented behavior of predicates in ExceptionGroup methods #99563

sobolevn opened this issue Nov 17, 2022 · 4 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@sobolevn
Copy link
Member

sobolevn commented Nov 17, 2022

I want to understand if my finding is a bug, or it works as intended and just needs a better explanation in the docs.

Right now the docs say:

   .. method:: subgroup(condition)

      Returns an exception group that contains only the exceptions from the
      current group that match *condition*, or ``None`` if the result is empty.

      The condition can be either a function that accepts an exception and returns
      true for those that should be in the subgroup, or it can be an exception type
      or a tuple of exception types, which is used to check for a match using the
      same check that is used in an ``except`` clause.

So, basically it promises that it will call this predicate for all exceptions in a group.

Let's test this:

>>> def p(e):
...    print(type(e), e)
...    return isinstance(e, ValueError)
... 
>>> eg = ExceptionGroup('message', [
...             ValueError(1), TypeError(2), ValueError(3),
...         ])
>>> eg.subgroup(p)
<class 'ExceptionGroup'> message (3 sub-exceptions)
<class 'ValueError'> 1
<class 'TypeError'> 2
<class 'ValueError'> 3
ExceptionGroup('message', [ValueError(1), ValueError(3)])

Notice that the first time that it is called, it uses ExceptionGroup argument.

But, let try to always return True this time:

>>> called_times = 0
>>> def p(e):
...    global called_times
...    called_times += 1
...    print(type(e), e)
...    return True
... 
>>> eg = ExceptionGroup('message', [
...             ValueError(1), TypeError(2), ValueError(3),
...         ])
>>> eg.subgroup(p)
<class 'ExceptionGroup'> message (3 sub-exceptions)
ExceptionGroup('message', [ValueError(1), TypeError(2), ValueError(3)])
>>> assert called_times == 1

Now it is only called once.

As far as I understand it works like this:

  1. Firstly, it tries to call the predicate function with ExceptionGroup itself
  2. If True is returned, it just uses this group as a result and ends
  3. If False is returned the first time, it then calls this function again for all the exceptions in a group

Code to illustrate that:

>>> called_times = 0
>>> def p(e):
...             global called_times
...             called_times += 1
...             print(type(e), e)
...             return called_times > 1
... 
>>> eg = ExceptionGroup('message', [
...             ValueError(1), TypeError(2), ValueError(3),
...         ])
>>> 
>>> eg.subgroup(p)
<class 'ExceptionGroup'> message (3 sub-exceptions)
<class 'ValueError'> 1
<class 'TypeError'> 2
<class 'ValueError'> 3
ExceptionGroup('message', [ValueError(1), TypeError(2), ValueError(3)])

Is it correct, @iritkatriel?
I found it while working on python/typeshed#9219

@sobolevn sobolevn added type-bug An unexpected behavior, bug, or error interpreter-core (Objects, Python, Grammar, and Parser dirs) labels Nov 17, 2022
@iritkatriel
Copy link
Member

So, basically it promises that it will call this predicate for all exceptions in a group.

No, it says below in the doc:

The condition is checked for all exceptions in the nested exception group, including the top-level and any nested exception groups. If the condition is true for such an exception group, it is included in the result in full.

Is it necessary to state also that it will not recurse into a subtree that is included in the result in its entirety?

@sobolevn
Copy link
Member Author

Thank you for the quick answer!

Is it necessary to state also that it will not recurse into a subtree that is included in the result in its entirety?

It might be worth it, if this is not just an implementation detail.

@iritkatriel
Copy link
Member

I'd rather not. It is an implementation detail. I don't think the doc currently implies otherwise.

@sobolevn
Copy link
Member Author

sobolevn commented Nov 17, 2022

Fair enough! From typeshed's perspective we will be happy with both :)

@sobolevn sobolevn closed this as not planned Won't fix, can't repro, duplicate, stale Nov 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

2 participants