Literal Comparison

See also

pylint literal-comparison (R0123)

tl;dr: it’s possible for x is 1 to be False, but x == 1 to be True, and when/whether this happens depends entirely on implementation details of Python. It is always a mistake to compare using is against a literal.

What the meaning of is is

For most types, most of the time, x == y means something like “x and y are interchangeable, as long as you aren’t mutating them”.

x is y, in contrast, has a strict and narrow meaning. It means “x refers to the same object as y”. As a consequence, it means x and y are interchangeable in all circumstances, even if you are mutating them!

Mutable Literals

Because mutable literals are defined to always evaluate to a new object, expressions like x is [] or x is {} will always evaluate to False, which is probably not the intended behavior.

Immutable Literals

Because is detects equivalence under mutation, and immutable objects cannot be mutated, it stands to reason that every equal immutable object could be identical, as long as they are really truly equivalent. This is the approach taken by PyPy, as well as the approach taken in e.g. JavaScript. To allow for this, Python allows for immutable literals to be the same object. For example:

>>> 1e6 is 1e6
True

But Python also allows for them to not be the same object:

>>> x = 1e6
>>> x is 1e6
False

Whether the an identical immutable literal expression is the same object, or a different but equal object, is implementation-defined. As a result, expressions like x is (), x is 1.0, x is 1, x is b"", or x is "" may evaluate to either True or False, or may even choose randomly between the two. It is always incorrect to compare literals in this way, because of the implementation-defined behavior.

Named Constants

True, False, and None are not included in this rule: if a piece of code should compare specifically against any of these three, it should use is. They are always the same object, and x is None is not buggy.

is for these values can be used to distinguish None or False from values like 0 or [], and True from values like 1 or [1]. For bools, howver, this kind of explicit comparison is rare: most of the time x is True can be better phrased as just x, and x is False can be better phrased as not x.

The Pedantic Section

x == y can’t and doesn’t literally mean that two objects are interchangeable as long as you don’t mutate them. For one thing, x is y may evaluate to something different than y is y.

A more complete definition would be something like: “are interchangeable as long as you don’t perform any identity-aware operations.”

But even that is not enough, as anyone can define __eq__. For example, mock.ANY compares equal to everything but is not equivalent to anything but mock.ANY. And the designers of floating point numbers were particularly cruel in defining a -0.0 that compares equal to 0.0, but has subtly different arithmetic behavior. And neither -0.0 nor 0.0 can be used as a list index, even though they both compare equal to 0, a valid list index.

There may be fewer things that follow the rule than that don’t. But in some spiritual sense, the idea behind == is interchangeability absent mutation and other identity-centered operations, and the rest is practical shortcuts.