Repository: psf/black
Pull request: Remove unnecessary parentheses from `except` clauses
<!-- Hello! Thanks for submitting a PR. To help make things go a bit more
     smoothly we would appreciate that you go through this template. -->

### Description

Closes #2937. As described in the issue we want to remove redundant parentheses from `except` clauses.

### Checklist - did you ...

- [X] Add a CHANGELOG entry if necessary?
- [X] Add / update tests if necessary?
- [X] Add new / update outdated documentation?


Problem statement:
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
```

Hints from issue 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.

Commit message: Remove unnecessary parentheses from Except clauses

Patch to review:
diff --git a/CHANGES.md b/CHANGES.md
index bb3ccb9ed9f..f7fdd1db3d4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -14,6 +14,8 @@
 
 <!-- Changes that affect Black's preview style -->
 
+- Remove unnecessary parentheses from `except` statements (#2939)
+
 ### _Blackd_
 
 <!-- Changes to blackd -->
diff --git a/src/black/linegen.py b/src/black/linegen.py
index 4dc242a1dfe..2a5105b0154 100644
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -313,7 +313,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 Preview.remove_except_parens in self.mode:
+            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=Ø)
diff --git a/src/black/mode.py b/src/black/mode.py
index 35a072c23e0..a091d432c92 100644
--- a/src/black/mode.py
+++ b/src/black/mode.py
@@ -127,6 +127,7 @@ class Preview(Enum):
     """Individual preview style features."""
 
     string_processing = auto()
+    remove_except_parens = auto()
 
 
 class Deprecated(UserWarning):
@@ -162,9 +163,7 @@ def __contains__(self, feature: Preview) -> bool:
         """
         if feature is Preview.string_processing:
             return self.preview or self.experimental_string_processing
-        # TODO: Remove type ignore comment once preview contains more features
-        #  than just ESP
-        return self.preview  # type: ignore
+        return self.preview
 
     def get_cache_key(self) -> str:
         if self.target_versions:
diff --git a/tests/data/remove_except_parens.py b/tests/data/remove_except_parens.py
new file mode 100644
index 00000000000..f49c837330c
--- /dev/null
+++ b/tests/data/remove_except_parens.py
@@ -0,0 +1,40 @@
+# 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
+
+# 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
diff --git a/tests/test_format.py b/tests/test_format.py
index 269bbacd249..64f4ad135ea 100644
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -79,6 +79,7 @@
     "long_strings__edge_case",
     "long_strings__regression",
     "percent_precedence",
+    "remove_except_parens",
 ]
 
 SOURCES: List[str] = [


Code review comments:
diff_hunk: @@ -127,6 +127,7 @@ class Preview(Enum):
     """Individual preview style features."""
 
     string_processing = auto()
+    remove_except_parens = auto()
path: src/black/mode.py
text: @author:
I currently have a similar PR (#2926) open that handles redundant brackets after `with` statements. 

Looking at the issues there a quite a few of these brackets cases that we'll need to add in so would you rather:
a) have a separate preview setting for each case (e.g. `remove_with_parens`, `remove_except_parens`, ...)
or
b) have a single preview setting for all these cases (e.g. `remove_redundant_parens`)

I'd lean towards a single preview setting (b) since feels a bit cleaner, but happy either way 😄 

@author:
^ Going to combine these

@user1:
I agree with combing 'em :)