PEP-654: except *T --> except* T (#2091)
This commit is contained in:
parent
389e8219cb
commit
957b7eb9b8
68
pep-0654.rst
68
pep-0654.rst
|
@ -532,11 +532,11 @@ exceptions can be handled by each ``except*`` clause:
|
|||
|
||||
try:
|
||||
...
|
||||
except *SpamError:
|
||||
except* SpamError:
|
||||
...
|
||||
except *FooError as e:
|
||||
except* FooError as e:
|
||||
...
|
||||
except *(BarError, BazError) as e:
|
||||
except* (BarError, BazError) as e:
|
||||
...
|
||||
|
||||
In a traditional ``try-except`` statement there is only one exception to handle,
|
||||
|
@ -572,7 +572,7 @@ Exceptions are matched using a subclass check. For example:
|
|||
|
||||
try:
|
||||
low_level_os_operation()
|
||||
except *OSError as eg:
|
||||
except* OSError as eg:
|
||||
for e in eg.exceptions:
|
||||
print(type(e).__name__)
|
||||
|
||||
|
@ -593,9 +593,9 @@ The order of ``except*`` clauses is significant just like with the regular
|
|||
|
||||
>>> try:
|
||||
... raise ExceptionGroup("problem", [BlockingIOError()])
|
||||
... except *OSError as e: # Would catch the error
|
||||
... except* OSError as e: # Would catch the error
|
||||
... print(repr(e))
|
||||
... except *BlockingIOError: # Would never run
|
||||
... except* BlockingIOError: # Would never run
|
||||
... print('never')
|
||||
...
|
||||
ExceptionGroup('problem', [BlockingIOError()])
|
||||
|
@ -619,9 +619,9 @@ recursively, using the ``split()`` method:
|
|||
... [TypeError('c'), KeyError('d')])
|
||||
... ]
|
||||
... )
|
||||
... except *TypeError as e1:
|
||||
... except* TypeError as e1:
|
||||
... print(f'e1 = {e1!r}')
|
||||
... except *Exception as e2:
|
||||
... except* Exception as e2:
|
||||
... print(f'e2 = {e2!r}')
|
||||
...
|
||||
e1 = ExceptionGroup('eg', [TypeError('b'), ExceptionGroup('nested', [TypeError('c')])])
|
||||
|
@ -644,9 +644,9 @@ clauses, the remaining part of the group is propagated on:
|
|||
... TypeError('c'), KeyError('e')
|
||||
... ]
|
||||
... )
|
||||
... except *ValueError as e:
|
||||
... except* ValueError as e:
|
||||
... print(f'got some ValueErrors: {e!r}')
|
||||
... except *TypeError as e:
|
||||
... except* TypeError as e:
|
||||
... print(f'got some TypeErrors: {e!r}')
|
||||
... except ExceptionGroup as e:
|
||||
... print(f'propagated: {e!r}')
|
||||
|
@ -670,7 +670,7 @@ message string. This is to make the type of ``e`` consistent and statically know
|
|||
|
||||
>>> try:
|
||||
... raise BlockingIOError
|
||||
... except *OSError as e:
|
||||
... except* OSError as e:
|
||||
... print(repr(e))
|
||||
...
|
||||
ExceptionGroup('', [BlockingIOError()])
|
||||
|
@ -683,7 +683,7 @@ naked form:
|
|||
>>> try:
|
||||
... try:
|
||||
... raise ValueError(12)
|
||||
... except *TypeError as e:
|
||||
... except* TypeError as e:
|
||||
... print('never')
|
||||
... except ValueError as e:
|
||||
... print(f'caught ValueError: {e!r}')
|
||||
|
@ -755,10 +755,10 @@ the original ``ExceptionGroup``:
|
|||
... [OSError(4), TypeError(5), ValueError(6)])
|
||||
... ]
|
||||
... )
|
||||
... except *ValueError as e:
|
||||
... except* ValueError as e:
|
||||
... print(f'*ValueError: {e!r}')
|
||||
... raise
|
||||
... except *OSError as e:
|
||||
... except* OSError as e:
|
||||
... print(f'*OSError: {e!r}')
|
||||
... except ExceptionGroup as e:
|
||||
... print(repr(e))
|
||||
|
@ -793,10 +793,10 @@ merged with the unhandled ``TypeErrors``.
|
|||
... [OSError(4), TypeError(5), ValueError(6)])
|
||||
... ]
|
||||
... )
|
||||
... except *ValueError as e:
|
||||
... except* ValueError as e:
|
||||
... print(f'*ValueError: {e!r}')
|
||||
... raise e
|
||||
... except *OSError as e:
|
||||
... except* OSError as e:
|
||||
... print(f'*OSError: {e!r}')
|
||||
... raise
|
||||
...
|
||||
|
@ -850,7 +850,7 @@ it into the new ``ExceptionGroup``.
|
|||
|
||||
>>> try:
|
||||
... raise ExceptionGroup("one", [ValueError('a'), TypeError('b')])
|
||||
... except *ValueError:
|
||||
... except* ValueError:
|
||||
... raise ExceptionGroup("two", [KeyError('x'), KeyError('y')])
|
||||
...
|
||||
| ExceptionGroup
|
||||
|
@ -897,7 +897,7 @@ chaining:
|
|||
|
||||
>>> try:
|
||||
... raise TypeError('bad type')
|
||||
... except *TypeError as e:
|
||||
... except* TypeError as e:
|
||||
... raise ValueError('bad value') from e
|
||||
...
|
||||
| ExceptionGroup
|
||||
|
@ -927,9 +927,9 @@ other clauses from the same ``try`` statement:
|
|||
|
||||
>>> try:
|
||||
... raise TypeError(1)
|
||||
... except *TypeError:
|
||||
... except* TypeError:
|
||||
... raise ValueError(2) from None # <- not caught in the next clause
|
||||
... except *ValueError:
|
||||
... except* ValueError:
|
||||
... print('never')
|
||||
...
|
||||
| ExceptionGroup
|
||||
|
@ -952,7 +952,7 @@ direct child of the new exception group created for that:
|
|||
|
||||
>>> try:
|
||||
... raise ExceptionGroup("eg", [ValueError('a')])
|
||||
... except *ValueError:
|
||||
... except* ValueError:
|
||||
... raise KeyError('x')
|
||||
...
|
||||
| ExceptionGroup
|
||||
|
@ -975,7 +975,7 @@ direct child of the new exception group created for that:
|
|||
>>>
|
||||
>>> try:
|
||||
... raise ExceptionGroup("eg", [ValueError('a'), TypeError('b')])
|
||||
... except *ValueError:
|
||||
... except* ValueError:
|
||||
... raise KeyError('x')
|
||||
...
|
||||
| ExceptionGroup
|
||||
|
@ -1013,7 +1013,7 @@ OS errors, while letting all other exceptions propagate.
|
|||
|
||||
try:
|
||||
low_level_os_operation()
|
||||
except *OSError as errors:
|
||||
except* OSError as errors:
|
||||
raise errors.subgroup(lambda e: e.errno != errno.EPIPE) from None
|
||||
|
||||
|
||||
|
@ -1031,7 +1031,7 @@ exception group. Any modifications to ``e`` will likely be lost:
|
|||
>>> eg.foo = 'foo'
|
||||
>>> try:
|
||||
... raise eg
|
||||
... except *TypeError as e:
|
||||
... except* TypeError as e:
|
||||
... e.foo = 'bar'
|
||||
... # ^----------- ``e`` is an ephemeral object that might get
|
||||
>>> # destroyed after the ``except*`` clause.
|
||||
|
@ -1052,7 +1052,7 @@ It is not possible to use both traditional ``except`` blocks and the new
|
|||
...
|
||||
except ValueError:
|
||||
pass
|
||||
except *CancelledError: # <- SyntaxError:
|
||||
except* CancelledError: # <- SyntaxError:
|
||||
pass # combining ``except`` and ``except*``
|
||||
# is prohibited
|
||||
|
||||
|
@ -1069,12 +1069,12 @@ ambiguous:
|
|||
|
||||
try:
|
||||
...
|
||||
except *ExceptionGroup: # <- Runtime error
|
||||
except* ExceptionGroup: # <- Runtime error
|
||||
pass
|
||||
|
||||
try:
|
||||
...
|
||||
except *(TypeError, ExceptionGroup): # <- Runtime error
|
||||
except* (TypeError, ExceptionGroup): # <- Runtime error
|
||||
pass
|
||||
|
||||
|
||||
|
@ -1121,7 +1121,7 @@ Once programs begin to use these features, there will be migration issues to
|
|||
consider:
|
||||
|
||||
* An ``except T:`` clause that wraps code which is now potentially raising
|
||||
an exception group may need to become ``except *T:``, and its body may
|
||||
an exception group may need to become ``except* T:``, and its body may
|
||||
need to be updated. This means that raising an exception group is an
|
||||
API-breaking change and will likely be done in new APIs rather than
|
||||
added to existing ones.
|
||||
|
@ -1308,7 +1308,7 @@ It is unlikely that asyncio users would want to do something like this:
|
|||
try:
|
||||
async with asyncio.TaskGroup() as g:
|
||||
g.create_task(task1); g.create_task(task2)
|
||||
except *KeyError:
|
||||
except* KeyError:
|
||||
# handling KeyError here is meaningless, there's
|
||||
# no context to do anything with it but to log it.
|
||||
|
||||
|
@ -1326,7 +1326,7 @@ exceptions in it.
|
|||
Not Matching Naked Exceptions in ``except*``
|
||||
--------------------------------------------
|
||||
|
||||
We considered the option of making ``except *T`` match only exception groups
|
||||
We considered the option of making ``except* T`` match only exception groups
|
||||
that contain ``Ts``, but not naked ``Ts``. To see why we thought this would
|
||||
not be a desirable feature, return to the distinction in the previous paragraph
|
||||
between operation errors and control flow exceptions. If we don't know whether
|
||||
|
@ -1352,7 +1352,7 @@ write a separate code block to handle each case:
|
|||
...
|
||||
except SomeError:
|
||||
# handle the naked exception
|
||||
except *SomeError:
|
||||
except* SomeError:
|
||||
# handle the exception group
|
||||
|
||||
|
||||
|
@ -1361,7 +1361,7 @@ Allow mixing ``except:`` and ``except*:`` in the same ``try``
|
|||
|
||||
This option was rejected because it adds complexity without adding useful
|
||||
semantics. Presumably the intention would be that an ``except T:`` block handles
|
||||
only naked exceptions of type ``T``, while ``except *T:`` handles ``T`` in
|
||||
only naked exceptions of type ``T``, while ``except* T:`` handles ``T`` in
|
||||
exception groups. We already discussed above why this is unlikely
|
||||
to be useful in practice, and if it is needed then the nested ``try-except``
|
||||
block can be used instead to achieve the same result.
|
||||
|
@ -1379,7 +1379,7 @@ specified in the same place where we state ``T``.
|
|||
Programming Without 'except \*'
|
||||
===============================
|
||||
|
||||
Consider the following simple example of the ``except *`` syntax (pretending
|
||||
Consider the following simple example of the ``except*`` syntax (pretending
|
||||
Trio natively supported this proposal):
|
||||
|
||||
.. code-block::
|
||||
|
@ -1389,7 +1389,7 @@ Trio natively supported this proposal):
|
|||
# Make two concurrent calls to child()
|
||||
nursery.start_soon(child)
|
||||
nursery.start_soon(child)
|
||||
except *ValueError:
|
||||
except* ValueError:
|
||||
pass
|
||||
|
||||
Here is how this code would look in Python 3.9:
|
||||
|
|
Loading…
Reference in New Issue