Skip to main content

is vs == and checking for None

Level 1: Level 1: Foundationseasy8 minidentityequalitynoneis-operator

Tell identity (is) apart from equality (==), and check for None the right way.

Identity and equality answer different questions

== asks "do these two objects hold the same value?" is asks "are these two names bound to the exact same object in memory?" Most of the time they agree, so it is tempting to treat them as interchangeable, right up until the day they disagree and a bug slips through code review.

Every value in Python is an object with a fixed identity, which you can inspect with id(). A variable is just a name pointing at one of those objects. is compares identities (roughly id(a) == id(b)), while == asks the left object to compare itself to the right one by calling its __eq__ method.

Check yourself
You build a = [1, 2, 3] on one line and b = [1, 2, 3] on the next. What do a == b and a is b report?
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)   # True  (same contents)
print(a is b)   # False (two separate list objects)

a and b hold equal contents, so == is True. But they are two different lists built at two different moments, so their identities differ and is is False.

Names point at objects
Step 1 / 3
a = [1, 2, 3]
b = [1, 2, 3]
c = a
Names
  • aL1
Objects (heap)
  • L1list
    [1, 2, 3]

One list object exists, and the name a points at it.

== compares what is inside the boxes. is compares which box. b matches a on contents only; c IS a.

That is the whole model in one line: == compares what is inside the boxes, is compares which box.

None is a singleton, so test it with is

There is exactly one None object in a running program. NoneType never creates a second one. That is why value is None is the idiomatic and correct test: you are checking against the one true None, not against something that merely equals it.

value = None
print(value is None)   # True

Style guides (PEP 8) and linters flag value == None. It usually works, but it routes through __eq__, which any class is free to override.

Check yourself
Your function starts with if value == None: return 0. One day it is handed a NumPy array. What goes wrong?

Why == None can bite you

== runs the object's own __eq__. A NumPy array, for instance, defines == to compare elementwise:

import numpy as np
arr = np.array([1, 2, 3])
arr == None            # array([False, False, False]), not a plain bool

Now if arr == None: raises a ValueError about the truth value of an array being ambiguous. Writing arr is None sidesteps all of that: it is a pure identity check that no class can redefine, and it is exactly what is_missing(value) should use.

Check yourself
x = 1000 and y = 1000 are written on two separate lines. What does x is y report?

Do not use is for numbers or strings

CPython caches small integers and some short strings, so is can look correct and then fail on larger values:

x = 1000
y = 1000
print(x == y)   # True  (always trust this for values)
print(x is y)   # may print False; never rely on it

Whether two equal ints share identity is an implementation detail. Use == for values, and reserve is for None (and True/False).

Guard before you touch a maybe-None

None supports very few operations. len(None) raises TypeError: object of type 'NoneType' has no len(). So check first, then act:

if value is None:
    return 0
return len(value)

Interview nuance: interviewers probe why is None beats == None. Identity is a constant-time pointer comparison that cannot be overridden and leans on None being a guaranteed singleton, so it is both faster and impossible to fool. Equality dispatches to __eq__, which is arbitrary user code whose result and cost you do not control.

Worked example (Python)
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)   # True  (equal contents)
print(a is b)   # False (different objects)

value = None
print(value is None)   # True

Apply

Your turn

The task this lesson builds to.

Implement is_missing(value): return True when value is None, otherwise False.

Use the is None test, not == None.

2 hints and 4 automated checks are waiting in the workspace.

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Implement none_safe_len(value): return len(value), but return 0 when value is None (so it never crashes).

For None return 0; for "abc" return 3.

3 hints and 4 automated checks are waiting in the workspace.