Skip to content

normalize-comparisons

normalize-comparisons rewrites a comparison so it states its check directly, turning a == or != test against None into an identity test, moving a leading constant to the right of its subject, and folding a leading not into the operator it negates. One traversal makes all three rewrites, each behind its own facet.

python
if 0 == n and x == None and not y in ys:
python
if n == 0 and x is None and y not in ys:

What the Rule Leaves Alone

A chained comparison stays as written whatever its operators, because 0 < n < 10 already reads in the order its values fall and rewriting one link of a == b == None would leave a chain mixing == with is.

A comparison with a comment anywhere inside it keeps its operand order, since a swap would leave the comment attached to whichever operand ended up on its line.

A comparison inside an f-string or t-string replacement field is left as written, the way the layout rules treat one.

Configuration

KeyTypeDefaultMeaning
enabledbooltrueTurns the rule on or off.
rewrite-identitybooltrueRewrites a == or != test against None to is or is not, and flags a test against True or False without rewriting it. false leaves every singleton comparison as written.
rewrite-negationbooltrueFolds a leading not into the in or is it negates, so not a in b reads a not in b. false keeps the outer not.
rewrite-operand-orderbooltrueSwaps the operands of a comparison whose constant side comes first, so 42 == n reads n == 42. false keeps the operand order as written.

Facets

Each facet defaults on, so the rewrites arrive together. Setting one to false stops that rewrite and leaves the rest running, and setting rewrite-identity to false also stops the boolean-literal diagnostic alongside the None rewrite.

The rewrites compose in one pass, so not x == None settles as x is not None without a second run. Grouping parentheses move with the operand they wrap, and reflow-parentheses removes any pair the fold leaves redundant.

rewrite-identity

rewrite-identity turns == None into is None and != None into is not None, because None is a singleton and a test against it is an identity test. An equality against a non-singleton constant keeps its ==, so None == 0 stays as written.

The same facet reports a test against True or False rather than rewriting it, since dropping the literal to test the bare operand changes the result for any non-boolean operand, in that 2 == True is false whereas if 2: runs its body.

rewrite-negation

rewrite-negation folds a leading not into the operator it negates, so not a in b reads a not in b and not a is b reads a is not b. Both folds are exact, because the language defines not in and is not as the negations of in and is, whereas __eq__ and __ne__ are independent methods, so not a == b keeps its not.

rewrite-operand-order

rewrite-operand-order flips a comparison whose constant side comes first, so 42 == n reads n == 42, and an ordered operator reverses as it crosses, which turns 0 < n into n > 0. A literal ranks as more constant than a SCREAMING_CASE name and both rank above an ordinary name, which is why LIMIT == size flips whereas FLOOR == LIMIT stays. A collection or arithmetic expression takes the lowest rank among its parts, so [a, 1] == xs stays where [0, 1] == xs flips.

The Canonical Case

value == None and fallback != None each compare against None with an equality operator. value == None becomes value is None and fallback != None becomes fallback is not None, because None is a singleton and PEP 8 prescribes the identity form for any comparison against it.

def classify(fallback, value):
    if value is None:
        return fallback
    if fallback is not None:
        return value
    return fallback
python

More Examples

LIMIT == size puts the SCREAMING_CASE name LIMIT ahead of the plain name size, and FLOOR == LIMIT sets two SCREAMING_CASE names against each other. LIMIT == size flips to size == LIMIT and FLOOR == LIMIT stays as written, because a SCREAMING_CASE name ranks as more constant than a plain name, whereas two names of the same rank leave no constant side to move.

The if not row in seen clause sits inside a list comprehension. Its leading not folds into the operator, so the clause is written as if row not in seen, because the rewrite reaches a comparison wherever it sits, so a comprehension condition folds the same way a statement condition does.

Each test leads with a literal, "__main__" == name, 0 < count, and 42 == count. The operands trade places so the variable leads, "__main__" == name becoming name == "__main__" and 42 == count becoming count == 42, and the ordered 0 < count reverses its operator as it flips, ending up as count > 0 so the check means the same thing.

not key in table and not key is sentinel each put a not ahead of the comparison. not key in table becomes key not in table and not key is sentinel becomes key is not sentinel, because the language defines not in and is not as the negations of in and is, so each fold restates the test exactly.

not value == None carries two rewrites at once, in that == None becomes an identity test and the leading not folds into the operator. The condition is written as value is not None in one pass, because both rewrites compose in one traversal rather than passing through an intermediate form.

flag == True and verbose != False each compare an operand against a boolean literal. Each comparison is reported and left as written, because dropping the literal to test the operand bare would change the result wherever the operand is not itself a boolean.

One if carries all three forms this rule rewrites, 0 == n, x == None, and not y in ys. All three settle together, the constant-led 0 == n flipping to n == 0, the equality x == None becoming the identity test x is None, and not y in ys folding into y not in ys.

For per-statement opt-outs, the Suppression chapter covers the # prose: skip[normalize-comparisons] directive, which covers every line a wrapped condition spans.