Skip to content

Typo in except statement#2

Open
hughdbrown wants to merge 2 commits intotalkpython:mainfrom
hughdbrown:bug-multiple-exception-types
Open

Typo in except statement#2
hughdbrown wants to merge 2 commits intotalkpython:mainfrom
hughdbrown:bug-multiple-exception-types

Conversation

@hughdbrown
Copy link

@hughdbrown hughdbrown commented Feb 8, 2026

Script can't run with Exception syntax.

Steps to replicate

  1. Confirm that talk-python-cli is not installed
  2. Install talk-python-cli using uv run (as seen in the README.md)
  3. Run sample command from the README.md

Error message

Fails with message that unparenthesized exceptions must be parenthesized:

packages/talk_python_cli/formatting.py", line 71
    except json.JSONDecodeError, TypeError:
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: multiple exception types must be parenthesized

The complete run

/tmp ❯ uv run -- talk-python-cli
error: Failed to spawn: `talk-python-cli`
  Caused by: No such file or directory (os error 2)

/tmp ❯ uv tool install talk-python-cli 
Resolved 17 packages in 124ms
Installed 17 packages in 15ms
 + anyio==4.12.1
 + attrs==25.4.0
 + certifi==2026.1.4
 + cyclopts==4.5.1
 + docstring-parser==0.17.0
 + docutils==0.22.4
 + h11==0.16.0
 + httpcore==1.0.9
 + httpx==0.28.1
 + idna==3.11
 + markdown-it-py==4.0.0
 + mdurl==0.1.2
 + pygments==2.19.2
 + rich==14.3.2
 + rich-rst==1.3.2
 + talk-python-cli==0.2.0
 + typing-extensions==4.15.0
Installed 1 executable: talkpython

/tmp ❯ talkpython episodes search "FastAPI"
Traceback (most recent call last):
  File "/Users/hughbrown/.local/bin/talkpython", line 4, in <module>
    from talk_python_cli.app import main
  File "/Users/hughbrown/.local/share/uv/tools/talk-python-cli/lib/python3.12/site-packages/talk_python_cli/app.py", line 15, in <module>
    from talk_python_cli.formatting import console, display_json, print_error
  File "/Users/hughbrown/.local/share/uv/tools/talk-python-cli/lib/python3.12/site-packages/talk_python_cli/formatting.py", line 71
    except json.JSONDecodeError, TypeError:
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: multiple exception types must be parenthesized

hughdbrown and others added 2 commits February 7, 2026 17:37
- Runs on all pushes and pull requests
- Checks only modified Python files for efficiency
- Validates formatting and syntax with ruff

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@mikeckennedy
Copy link
Member

Hey Hugo! Thanks for submitting this. I'm certain up for adding ruff auto-check. But the except (A, B): vs. except A, B: this capability has only been valid since Python 3.14 (see PEP 758). So even on 3.13 that would cause a crash. I'm up for using newer features, but that's a bit early if that's the only difference.

Maybe split the PR? I'd love to accept your contribution.

@hughdbrown
Copy link
Author

Perplexity says this has been around since version 2.x:
https://www.perplexity.ai/search/in-which-version-of-python-was-uWUIpmnvQw.VOwZ8srX6TQ#0

Catching multiple exceptions in a single except clause using a tuple has been supported since early Python 2 (at least 2.0), and it is present in all modern Python 2.x and 3.x versions.

this capability has only been valid since Python 3.14
I think you are reading PEP 758 backwards. Use of a parenthesized tuple for multiple exceptions was required until 3.14 when it was relaxed.

----- PEP 758
The current syntax for catching multiple exceptions requires parentheses in the except expression (equivalently for the except* expression). For example:

try:
...
except (ExceptionA, ExceptionB, ExceptionC):
...
While this syntax is clear and unambiguous, it can be seen as unnecessarily verbose in some cases, especially when catching a large number of exceptions. By allowing the omission of parentheses, we can simplify the syntax:

try:
...
except ExceptionA, ExceptionB, ExceptionC:
...

I was able to get this construct to run on every version from 3.9 to 3.14 (I could not easily install a version before 3.9):

Python 3.14.2 (main, Dec 5 2025, 16:49:16) [Clang 17.0.0 (clang-1700.6.3.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

try:
... print(1 / 0)
... except (ZeroDivisionError, ValueError, NameError):
... print("Exception")
...
Exception

Python 3.13.12 (main, Feb 3 2026, 17:53:27) [Clang 17.0.0 (clang-1700.6.3.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

try:
... print(1 / 0)
... except (ZeroDivisionError, ValueError, NameError):
... print("Exception")
...
Exception

Python 3.12.12 (main, Oct 9 2025, 11:07:00) [Clang 17.0.0 (clang-1700.6.3.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

try:
... print(1 / 0)
... except (ZeroDivisionError, ValueError, NameError):
... print("Exception")
...
Exception

Python 3.11.14 (main, Oct 9 2025, 16:16:55) [Clang 17.0.0 (clang-1700.6.3.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

try:
... print(1 / 0)
... except (ZeroDivisionError, ValueError, NameError):
... print("Exception")
...
Exception

Python 3.10.19 (main, Oct 9 2025, 15:25:03) [Clang 17.0.0 (clang-1700.6.3.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

try:
... print(1 / 0)
... except (ZeroDivisionError, ValueError, NameError):
... print("Exception")
...
Exception

Python 3.9.25 (main, Oct 31 2025, 18:40:52)
[Clang 17.0.0 (clang-1700.4.4.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

try:
... print(1 / 0)
... except (ZeroDivisionError, ValueError, NameError):
... print("Exception")
...
Exception

@mikeckennedy
Copy link
Member

Hey Hugh,

Perplexity might be right, but not in the way you're thinking.

In Python 2 there as this style:

try:
    bad_function()
except DivisionByZero, e:
    print('Oops, divide by zero!, Details:', e)

So when it sees:

try:
    bad_function()
except DivisionByZero, FileNotFoundError:
    print('Oh! Div or File!')

It defines a variable or sets the value of FileNotFoundError to the exception itself.

Meanwhile, running this except DivisionByZero, FileNotFoundError code in Python 3.8, we get:

uv run --python 3.8 main_no_parens.py
  File "main_no_parens.py", line 11
    except DivisionByZero, FileNotFoundError:
                         ^
SyntaxError: invalid syntax

In fact you get the same error in 3.9, 3.10, 3.11 and 3.12.

But a slightly more friendly error which is a feature of 3.13:

 uv run --python 3.13 main_no_parens.py
  File "/Users/michaelkennedy/Desktop/testing-exceptions/main_no_parens.py", line 11
    except DivisionByZero, FileNotFoundError:
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: multiple exception types must be parenthesized

Still it will NOT run in anything less than 3.14:

uv run --python 3.14 main_no_parens.py
Testing no parens, running on 3.14.3 (main, Feb  4 2026, 01:51:49) [Clang 21.1.4 ]
Here comes a baddy! Divide by zero
Oh! Div or File!

Success!

This is why I do not want to accept the PR that removes the parentheses. It works, but not as you would expect in Python 2, and it fails in Python 3.13 and below.

Here is the full code I tested against:

import random
import sys
from decimal import DivisionByZero

baddies = [
    ('Divide by zero', DivisionByZero()),
    ('Value error', ValueError()),
    ('General exception', Exception()),
    ('File not found', FileNotFoundError()),
]


def main():
    print(f'Testing parens, running on {sys.version}')
    try:
        bad_function()
    except DivisionByZero, FileNotFoundError:
        print('Oh! Div or File!')
    except ValueError:
        print('Value is bad!')
    except Exception:
        print('Other bad things')


def bad_function():
    baddy = random.choice(baddies)
    print(f'Here comes a baddy! {baddy[0]}')
    raise baddy[1]


if __name__ == '__main__':
    main()

@hughdbrown
Copy link
Author

This is why I do not want to accept the PR that removes the parentheses.

I am not removing the parentheses. I am adding them.

diff --git a/src/talk_python_cli/formatting.py b/src/talk_python_cli/formatting.py
index f6257f0..d0712dd 100644
--- a/src/talk_python_cli/formatting.py
+++ b/src/talk_python_cli/formatting.py
@@ -68,7 +68,7 @@ def display_json(content: str) -> None:
     """Output JSON content — pretty-printed if on a TTY, raw otherwise."""
     try:
         data = json.loads(content)
-    except json.JSONDecodeError, TypeError:
+    except (json.JSONDecodeError, TypeError):
         # Server may have returned Markdown even though JSON was requested;
         # fall back to printing the raw text.
         console.print(content)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants