Skip to main content

Picky Parentheses

Picky Parentheses is a flake8 plugin that nitpicks all things parentheses, brackets and braces. The plugin has two components:

  1. A checker that warns about redundant parentheses (with some exceptions).
  2. A checker for parentheses, brackets, and braces alignment. This component is very opinionated but has its own error codes so you can easily disable it.

Table of Contents

Installation and Usage

This is a plugin for flake8. It supports Python 3.7 - 3.14.
Refer to the documentation of flake8 on how to run it on your code: https://flake8.pycqa.org/en/latest/

Two common options are to either install the plugin and then run flake8:

pip install flake8-picky-parentheses

flake8 '<path/to/your/code>'

Or to let flake8 fetch the plugin for you (requires flake8 >= 5.0.0):

flake8 --require-plugins flake8-picky-parentheses '<path/to/your/code>'

If you only want to run this plugin and bypass any other flake8 checks, you can use the --select option:

flake8 [other options] --select='PAR0,PAR1' '<path/to/your/code>'

Where PAR0 is the code for the redundant parentheses checker and PAR1 is the code for the parentheses alignment checker.

If you, in turn want to disable the opinionated parentheses alignment checker, you can use the --ignore or --extend-ignore option:

flake8 [other options] --extend-ignore='PAR1' '<path/to/your/code>'

Error Codes

These are the error codes which you can get using this plugin:

Code Brief Description
PAR0xx [Group] Redundant parentheses
PAR001 Redundant parentheses (general)
PAR002 Parentheses used for tuple unpacking
PAR1xx [Group] (Opinionated) parentheses, brackets, braces not well-aligned
PAR101 Opening bracket is last, but closing is not on new line
PAR102 Closing bracket has different indentation than the line with the opening bracket
PAR103 Consecutive opening brackets at the end of the line must have consecutive closing brackets.
PAR104 Only operators and comments are allowed after a closing bracket on a new line

PAR0xx

These are the error codes for the redundant parentheses checker.

PAR001

It means that you use redundant parentheses, and they do not help readability. For example:

# BAD
a = (("a", "b"))

PAR002

It means that you use parentheses for an unpacking expression. For example:

# BAD
(a,) = "b"

PAR1xx

These are the error codes for the opinionated alignment checker.

PAR101

It means that the opening bracket is last in its line, but closing one is not on a new line. For example:

# BAD
if (
        a == b):
    c + d

# GOOD
if (
    a == b
):
    c + d

# BAD
a = [
    1, 2,
    3, 4]

# GOOD
a = [
    1, 2,
    3, 4
]

# GOOD
a = [1, 2,
     3, 4]

PAR102

It means that closing bracket is on new line, but there is a indentation mismatch. For example:

# BAD
if (
    a == b
        ):
    c + d

# GOOD
if (
    a == b
):
    c + d

# BAD
a = [
    1, 2,
    3, 4
    ]

# GOOD
a = [
    1, 2,
    3, 4
]

PAR103

It means that consecutive opening brackets at the end of a line must have consecutive closing brackets.

# BAD
answer = func((
    1, 2, 3, 4, 5,
    )
)

# GOOD
answer = func((
    1, 2, 3, 4, 5,
))

PAR104

Only operators and comments are allowed after a closing bracket on a new line.

# BAD
a = func(
    1, 2, 3, 4, 5
) + 6

# GOOD
a = (
    func(
        1, 2, 3, 4, 5
    )
    + 6
)

Details and Exceptions

The redundant parentheses checker uses Python's tokenize and ast module to try to remove each pair of parentheses and see if the code still compiles and yields the same AST (i.e., is semantically equivalent). If it does, a flake (lint error) is reported. However, there are some notable exceptions to this rule:

  1. Parentheses for tuple literals.
    • Python 3.14+ allows to omit parentheses for multi-error except/except*-expressions without as-clause. This exception means that while parentheses in such cases is redundant, they're still allowed by this linter.
  2. A single pair or parentheses in expressions to highlight operator precedence. Even if these parentheses are redundant, they help to divide parts of expressions and show sequence of actions.
  3. Parts of slices.
  4. Multi-line1) expression, if and for parts in comprehensions.
  5. Multi-line1) keyword arguments or argument defaults.
  6. String concatenation over several lines in lists, tuples, and function arguments.

Exception type 1:

a = ("a",)     # GOOD
a = "a",       # GOOD
a = ("a")      # BAD
a = (("a"),)   # BAD
a = (("a",))   # BAD
foo(("a",))    # GOOD
foo("a",)      # BAD

Exception type 2:

a = (1 + 2) + 3            # GOOD
a = (1 + 2) % 3            # GOOD
a = 1 and (2 + 3)          # GOOD
a = (1 / 2) * 3            # GOOD
a = not (1 + 2)            # GOOD
a = (not 1) + 2            # GOOD
a = 1 + (2 if a else 3)    # GOOD
a = foo(*(a if b else c))  # GOOD
a = foo(*(a + b))          # GOOD
a = foo(**(a + b))         # GOOD
a = (1 + 2)                # BAD
a = 1 + (2)                # BAD
a = ((not 1)) + 2          # BAD
a = foo(*(a))              # BAD
a = foo(**(a))             # BAD

Exception type 3:

foo[(1 + 2):10]    # GOOD
foo[1:(1 + 2)]     # GOOD
foo[1:5:(1 + 1)]   # GOOD
foo[:(-bar)]       # GOOD
foo[(1):]          # BAD
foo[:(1)]          # BAD

Exception type 4:

# GOOD
a = (
    b for b in c
    if (
        some_thing == other_thing
        or whatever_but_long
    )
)

# GOOD
a = [
    b for b in c
    if (b
        in d)
]

# BAD
a = (
    b for b in c
    if (b in d)
)

# GOOD
a = (
    b for b in (c
                + d)
)

# BAD
a = (
    b for b in (c + d)
)

# GOOD
a = (
    (
        1
        + b
    )
    for b in c
)

# BAD
a = (
    (1 + b) for b in c
)

# GOOD
a = {
    (
        "foo%s"
        % b
    ): (
        b
        * 2
    )
    for b in c
}

Exception type 5:

# GOOD
foo(bar=(a
         in b))

# BAD
foo(bar=(a in b))

# GOOD
def foo(bar=(a
             is b)):
    ...

# BAD
def foo(bar=(a is b)):
    ...

Exception type 6:

# GOOD
[
    "a",
    (
        "b"
        "c"
    ),
    "d",
]

# This helps to avoid forgetting a comma at the end of a string spanning
# multiple lines. Compare with:
[
    "a",
    "b"
    "c",
    "d",
]
# Was the comma after "b" forgotten or was the string supposed to be "bc"?

# BAD
[
    (
        "a" "b"
    ),
]

# This also applies to function calls:
# GOOD
func(
    (
        "a"
        "b"
    ),
    "c",
)

Footnotes:

  1. Multi-line means that either
    • the expression spans multiple lines, e.g.,
      (a
       + b)
      
    • or the first part of the expression is on a new line (e.g., if a name is very long), e.g.,
      (
          veeeeeeeeeeery_looooooong_name
      )
      
      but also
      (
          a
          + b
      )
      
    Multi-line expressions do not include
    (a + b
    )
    

Additional Notes

This plugin was developed to improve the code quality of Neo4j Python projects.

Release files for flake8-picky-parentheses 0.6.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for flake8-picky-parentheses 0.6.2
File Size Uploaded
flake8_picky_parentheses-0.6.2.tar.gz 28.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for flake8-picky-parentheses 0.6.2
File Interpreter ABI Platform
flake8_picky_parentheses-0.6.2-py3-none-any.whl Python 3 none any Details

Total release size: 47.4 kB

Release files / flake8_picky_parentheses-0.6.2.tar.gz

Download URL flake8_picky_parentheses-0.6.2.tar.gz
Size 28.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7533b24b5e002c6f3a2e1b6b0412114e73d0d87419c2b65470c94efb1722df4c
BLAKE2b-256 checksum
How to use checksums
6c80fd3eb45bb3f5bcbda812d3f9ed2451e448410dd383e3cd461f9a8b80b3b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jan 10, 2026.

Transparency log

Release files / flake8_picky_parentheses-0.6.2-py3-none-any.whl

Download URL flake8_picky_parentheses-0.6.2-py3-none-any.whl
Size 19.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
83f8459e95c0bf60d5951212e3c955bd4998cceb56eb882ff3b0efd7c2e66664
BLAKE2b-256 checksum
How to use checksums
85e4826202567e116cc41b63fd6efa35c17e52ac80e0f7ef0da4bae028cedfc8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jan 10, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.2 This release

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.5

1 release file

0.5.4

1 release file

0.5.3

1 release file

0.5.2

1 release file

0.5.1

1 release file

0.5.0

1 release file

0.4.0

1 release file

0.3.2

1 release file

0.3.1

1 release file

0.3.0

1 release file

0.2.0

1 release file

0.1.2

1 release file

0.1.1

1 release file

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page