Title: Introducing try..except* · Issue #4 · python/exceptiongroups · GitHub
Open Graph Title: Introducing try..except* · Issue #4 · python/exceptiongroups
X Title: Introducing try..except* · Issue #4 · python/exceptiongroups
Description: The design discussed in this issue has been consolidated in https://github.com/python/exceptiongroups/blob/master/except_star.md. Disclaimer I'm going to be using the ExceptionGroup name in this issue, even though there are other alterna...
Open Graph Description: The design discussed in this issue has been consolidated in https://github.com/python/exceptiongroups/blob/master/except_star.md. Disclaimer I'm going to be using the ExceptionGroup name in this is...
X Description: The design discussed in this issue has been consolidated in https://github.com/python/exceptiongroups/blob/master/except_star.md. Disclaimer I'm going to be using the ExceptionGroup name in thi...
Opengraph URL: https://github.com/python/exceptiongroups/issues/4
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Introducing try..except*","articleBody":"### The design discussed in this issue has been consolidated in https://github.com/python/exceptiongroups/blob/master/except_star.md.\r\n\r\n-----\r\n\r\n### Disclaimer\r\n\r\n* I'm going to be using the `ExceptionGroup` name in this issue, even though there are other alternatives, e.g. `AggregateException`. Naming of the \"exception group\" object is outside of the scope of this issue.\r\n* This issue is primarily focused on discussing the new syntax modification proposal for the `try..except` construct, shortly called \"except*\".\r\n* I use the term \"naked\" exception for regular Python exceptions **not wrapped** in an ExceptionGroup. E.g. a regular `ValueError` propagating through the stack is \"naked\".\r\n* I assume that `ExceptionGroup` would be an iterable object. E.g. `list(ExceptionGroup(ValueError('a'), TypeError('b')))` would be equal to `[ValueError('a'), TypeError('b')]`\r\n* I assume that `ExceptionGroup` won't be an indexable object; essentially it's similar to Python `set`. The motivation for this is that exceptions can occur in random order, and letting users write `group[0]` to access the \"first\" error is error prone. The actual implementation of `ExceptionGroup` will likely use an ordered list of errors though.\r\n* I assume that `ExceptionGroup` will be a subclass of `BaseException`, which means it's assignable to `Exception.__context__` and can be directly handled with `except ExceptionGroup`.\r\n* The behavior of good and old regular `try..except` will not be modified.\r\n\r\n### Syntax\r\n\r\nWe're considering to introduce a new variant of the `try..except` syntax to simplify working with exception groups:\r\n\r\n```python\r\ntry:\r\n ...\r\nexcept *SpamError:\r\n ...\r\nexcept *BazError as e:\r\n ...\r\nexcept *(BarError, FooError) as e:\r\n ...\r\n```\r\n\r\nThe new syntax can be viewed as a variant of the tuple unpacking syntax. The `*` symbol indicates that zero or more exceptions can be \"caught\" and processed by one `except *` clause.\r\n\r\nWe also propose to enable \"unpacking\" in the `raise` statement:\r\n\r\n```python\r\nerrors = (ValueError('hello'), TypeError('world'))\r\nraise *errors\r\n```\r\n\r\n### Semantics\r\n\r\n#### Overview\r\n\r\nThe `except *SpamError` block will be run if the `try` code raised an `ExceptionGroup` with one or more instances of `SpamError`. It would also be triggered if a naked instance of `SpamError` was raised.\r\n\r\nThe `except *BazError as e` block would aggregate all instances of `BazError` into a list, wrap that list into an `ExceptionGroup` instance, and assign the resultant object to `e`. The type of `e` would be `ExceptionGroup[BazError]`. If there was just one naked instance of `BazError`, it would be wrapped into a list and assigned to `e`.\r\n\r\nThe `except *(BarError, FooError) as e` would aggregate all instances of `BarError` or `FooError` into a list and assign that wrapped list to `e`. The type of `e` would be `ExceptionGroup[Union[BarError, FooError]]`.\r\n\r\nEven though every `except*` star can be called only once, any number of them can be run during handling of an `ExceptionGroup`. E.g. in the above example, both `except *SpamError:` and `except *(BarError, FooError) as e:` could get executed during handling of one `ExceptionGroup` object, or all of the `except*` clauses, or just one of them.\r\n\r\nIt is not allowed to use both regular `except` clauses and the new `except*` clauses in the same `try` block. E.g. the following example would raise a `SyntaxErorr`:\r\n\r\n```python\r\ntry:\r\n ...\r\nexcept ValueError:\r\n pass\r\nexcept *CancelledError:\r\n pass\r\n```\r\n\r\nExceptions are mached using a subclass check. For example:\r\n\r\n```python\r\ntry:\r\n low_level_os_operation()\r\nexcept *OSerror as errors:\r\n for e in errors:\r\n print(type(e).__name__)\r\n```\r\n\r\ncould output:\r\n\r\n```\r\nBlockingIOError\r\nConnectionRefusedError\r\nOSError\r\nInterruptedError\r\nBlockingIOError\r\n```\r\n\r\n#### New raise* Syntax\r\n\r\nThe new `raise *` syntax allows to users to only process some exceptions out of the matched set, e.g.:\r\n\r\n```python\r\ntry:\r\n low_level_os_operation()\r\nexcept *OSerror as errors:\r\n new_errors = []\r\n for e in errors:\r\n if e.errno != errno.EPIPE:\r\n new_errors.append(e)\r\n raise *new_errors\r\n```\r\n\r\nThe above code ignores all `EPIPE` OS errors, while letting all others propagate.\r\n\r\n`raise *` syntax is special: it effectively extends the exception group with a list of errors without creating a new `ExceptionGroup` instance:\r\n\r\n```python\r\ntry:\r\n raise *(ValueError('a'), TypeError('b'))\r\nexcept *ValueError:\r\n raise *(KeyError('x'), KeyError('y'))\r\n\r\n# would result in: \r\n# ExceptionGroup({KeyError('x'), KeyError('y'), TypeError('b')})\r\n```\r\n\r\nA regular raise would behave similarly:\r\n\r\n```python\r\ntry:\r\n raise *(ValueError('a'), TypeError('b'))\r\nexcept *ValueError:\r\n raise KeyError('x')\r\n\r\n# would result in: \r\n# ExceptionGroup({KeyError('x'), TypeError('b')})\r\n```\r\n\r\n`raise *` accepts arguments of type `Iterable[BaseException]`.\r\n\r\n#### Unmatched Exceptions\r\n\r\nExample:\r\n\r\n```python\r\ntry:\r\n raise *(ValueError('a'), TypeError('b'), TypeError('c'), KeyError('e'))\r\nexcept *ValueError as e:\r\n print(f'got some ValueErrors: {e}')\r\nexcept *TypeError as e:\r\n print(f'got some TypeErrors: {e}')\r\n raise *e\r\n```\r\n\r\nThe above code would print:\r\n\r\n```\r\ngot some ValueErrors: ExceptionGroup({ValueError('a')})\r\ngot some TypeErrors: ExceptionGroup({TypeError('b'), TypeError('c')})\r\n```\r\n\r\nAnd then crash with an unhandled `KeyError('e')` error.\r\n\r\nBasically, before interpreting `except *` clauses, the interpreter will have an exception group object with a list of exceptions in it. Every `except *` clause, evaluated from top to bottom, can filter some of the exceptions out of the group and process them. In the end, if the exception group has no exceptions left in it, it wold mean that all exceptions were processed. If the exception group has some unprocessed exceptions, the current frame will be \"pushed\" to the group's traceback and the group would be propagated up the stack.\r\n\r\n#### Exception Chaining\r\n\r\nIf an error occur during processing a set of exceptions in a `except *` block, all matched errors would be put in a new `ExceptionGroup` which would have its `__context__` attribute set to the just occurred exception:\r\n\r\n```python\r\ntry:\r\n raise *(ValueError('a'), ValueError('b'), TypeError('z'))\r\nexcept *ValueError:\r\n 1 / 0\r\n\r\n# would result in:\r\n#\r\n# ExceptionGroup({\r\n# TypeError('z'),\r\n# ZeroDivisionError()\r\n# })\r\n#\r\n# where the `ZeroDivizionError()` instance would have\r\n# its __context__ attribute set to\r\n#\r\n# ExceptionGroup({\r\n# ValueError('a'), ValueError('b')\r\n# })\r\n```\r\n\r\nIt's also possible to explicitly chain exceptions:\r\n\r\n```python\r\ntry:\r\n raise *(ValueError('a'), ValueError('b'), TypeError('z'))\r\nexcept *ValueError as errors:\r\n raise RuntimeError('unexpected values') from errors\r\n\r\n# would result in:\r\n#\r\n# ExceptionGroup(\r\n# TypeError('z'),\r\n# RuntimeError('unexpected values')\r\n# )\r\n#\r\n# where the `RuntimeError()` instance would have\r\n# its __cause__ attribute set to\r\n#\r\n# ExceptionGroup({\r\n# ValueError('a'), ValueError('b')\r\n# })\r\n```\r\n\r\n### See Also\r\n\r\n* An analysis of how exception groups will likely be used in asyncio programs: https://github.com/python/exceptiongroups/issues/3#issuecomment-716203284\r\n* A WIP implementation of the `ExceptionGroup` type by @iritkatriel tracked here: [GitHub - iritkatriel/cpython at exceptionGroup](https://github.com/iritkatriel/cpython/tree/exceptionGroup)\r\n","author":{"url":"https://github.com/1st1","@type":"Person","name":"1st1"},"datePublished":"2020-10-26T02:41:23.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":13},"url":"https://github.com/4/exceptiongroups/issues/4"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:fb922919-6b05-a690-a4a8-b976dea590aa |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | B608:7F615:AFED3A:ECF90F:696996F0 |
| html-safe-nonce | c2acfdb8106d2d4c6653bacbce7aad95537714c630eeafc0beba38a85d4f8e05 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCNjA4OjdGNjE1OkFGRUQzQTpFQ0Y5MEY6Njk2OTk2RjAiLCJ2aXNpdG9yX2lkIjoiNTU2NTA5NzIxODYzNzY2NjAzMiIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 92ca7d5431f807bb90b93b480ad31dff7df3cc6a067795e222c8b4f25016ea0e |
| hovercard-subject-tag | issue:729190689 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/python/exceptiongroups/4/issue_layout |
| twitter:image | https://opengraph.githubassets.com/93f3ca33cbb58c031cf16516413352138f4b70b4020918029e1cbd1928168a77/python/exceptiongroups/issues/4 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/93f3ca33cbb58c031cf16516413352138f4b70b4020918029e1cbd1928168a77/python/exceptiongroups/issues/4 |
| og:image:alt | The design discussed in this issue has been consolidated in https://github.com/python/exceptiongroups/blob/master/except_star.md. Disclaimer I'm going to be using the ExceptionGroup name in this is... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | 1st1 |
| hostname | github.com |
| expected-hostname | github.com |
| None | 3542e147982176a7ebaa23dfb559c8af16f721c03ec560c68c56b64a0f35e751 |
| turbo-cache-control | no-preview |
| go-import | github.com/python/exceptiongroups git https://github.com/python/exceptiongroups.git |
| octolytics-dimension-user_id | 1525981 |
| octolytics-dimension-user_login | python |
| octolytics-dimension-repository_id | 228462148 |
| octolytics-dimension-repository_nwo | python/exceptiongroups |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 228462148 |
| octolytics-dimension-repository_network_root_nwo | python/exceptiongroups |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | af80af7cc9e3de9c336f18b208a600950a3c187c |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width