Repository: psf/black

Issue:
Remove redundant brackets from `except` clause
**Is your feature request related to a problem? Please describe.**

The except clause can accept a parenthesised tuple of exceptions. However, in the case of only a single exception the brackets should be removed.

Black currently leaves both of these examples unchanged:
```
a = 1
try:
    a.something
except (AttributeError) as err:
    raise err
```
and
```
a = 1
try:
    a.something
except (AttributeError,) as err:
    raise err
```

**Describe the solution you'd like**

I would expect both examples to be corrected to:
```
a = 1
try:
    a.something
except AttributeError as err:
    raise err
```

**Describe alternatives you've considered**

I could see an argument to leave the tuple example (with the trailing comma) as is, maybe the user wants to add more exceptions in the future. Easy either way on that one, but we should at least fix the first (non-tuple) example.

**Additional context**

I actually came across this when someone assumed the `except` was a function in a PR. Black added the space before the brackets, but also removing the brackets would really help highlight that it's a keyword not a function 😄 
```
try:
    a.something
except(AttributeError) as err:
    raise err
```


Discussion:
Agree, this goes with a lot of other cases where we should remove redundant parens.

Not sure about the tuple case; that would also change the AST.

Resolving patch:
diff --git a/src/black/linegen.py b/src/black/linegen.py
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -318,7 +318,12 @@ def __post_init__(self) -> None:
         self.visit_try_stmt = partial(
             v, keywords={"try", "except", "else", "finally"}, parens=Ø
         )
-        self.visit_except_clause = partial(v, keywords={"except"}, parens=Ø)
+        if self.mode.preview:
+            self.visit_except_clause = partial(
+                v, keywords={"except"}, parens={"except"}
+            )
+        else:
+            self.visit_except_clause = partial(v, keywords={"except"}, parens=Ø)
         self.visit_with_stmt = partial(v, keywords={"with"}, parens=Ø)
         self.visit_funcdef = partial(v, keywords={"def"}, parens=Ø)
         self.visit_classdef = partial(v, keywords={"class"}, parens=Ø)


Test patch:
diff --git a/tests/data/remove_except_parens.py b/tests/data/remove_except_parens.py
new file mode 100644
--- /dev/null
+++ b/tests/data/remove_except_parens.py
@@ -0,0 +1,79 @@
+# These brackets are redundant, therefore remove.
+try:
+    a.something
+except (AttributeError) as err:
+    raise err
+
+# This is tuple of exceptions.
+# Although this could be replaced with just the exception,
+# we do not remove brackets to preserve AST.
+try:
+    a.something
+except (AttributeError,) as err:
+    raise err
+
+# This is a tuple of exceptions. Do not remove brackets.
+try:
+    a.something
+except (AttributeError, ValueError) as err:
+    raise err
+
+# Test long variants.
+try:
+    a.something
+except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error) as err:
+    raise err
+
+try:
+    a.something
+except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,) as err:
+    raise err
+
+try:
+    a.something
+except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error) as err:
+    raise err
+
+# output
+# These brackets are redundant, therefore remove.
+try:
+    a.something
+except AttributeError as err:
+    raise err
+
+# This is tuple of exceptions.
+# Although this could be replaced with just the exception,
+# we do not remove brackets to preserve AST.
+try:
+    a.something
+except (AttributeError,) as err:
+    raise err
+
+# This is a tuple of exceptions. Do not remove brackets.
+try:
+    a.something
+except (AttributeError, ValueError) as err:
+    raise err
+
+# Test long variants.
+try:
+    a.something
+except (
+    some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error
+) as err:
+    raise err
+
+try:
+    a.something
+except (
+    some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
+) as err:
+    raise err
+
+try:
+    a.something
+except (
+    some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
+    some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,
+) as err:
+    raise err
diff --git a/tests/test_format.py b/tests/test_format.py
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -80,6 +80,7 @@
     "long_strings__edge_case",
     "long_strings__regression",
     "percent_precedence",
+    "remove_except_parens",
     "remove_for_brackets",
     "one_element_subscript",
 ]