None and Identity

Concepts covered: Data Types

A variable that exists but has nothing meaningful to hold yet needs a value that says so. Python provides exactly one: None. It has its own type, NoneType, and there is only ever 1 None object in a running program. Using 0 or an empty string for missing data confuses the absence of a value with a real value that happens to be 0 or empty.

refund = None
print(refund)
print(type(refund).__name__)
print(refund is None)
print(refund is not None)
amount = 0
print(amount is None, amount == 0)

Output:

None
NoneType
True
False
False True

refund is None: no refund has been recorded. amount is 0: a refund of zero dollars was recorded. Those are 2 different facts, and the last line shows the test that tells them apart. is None asks whether the variable points at the one None object; == 0 asks whether the value is numerically zero.

is compares identity, == compares value

Every object in a program has an identity, a number that stays fixed for the object's lifetime, and id() returns it. a is b is True exactly when id(a) == id(b): the 2 names refer to one object. a == b asks a looser question, whether the 2 values count as equal, and 2 separate objects can answer yes.

a = ["x", "y"]
b = ["x", "y"]
c = a
print(a == b, a is b)
print(a is c, id(a) == id(c))

Output:

True False
True True

a and b are 2 lists built separately with the same contents: equal, and separate. c = a adds a name to the first list, so a is c holds. This is the aliasing picture from the augmented assignment section, now with the operator that tests for it directly.

QuestionOperatorExampleResult
Same contents?`==``[1] == [1]``True`
Same object?`is``[1] is [1]``False`
Missing?`is None``x = None` then `x is None``True`
Present?`is not None``0 is not None``True`

Why None gets is and everything else gets ==

refund == None also prints True, so why does Python's style guide ask for is None? Because None is a single object, identity is the precise question, and is cannot be redefined. A type may define its own meaning for ==, and some do, which can make == None return something other than a plain True or False. is has no such escape hatch. The reverse applies to numbers and strings: they can be equal while living in separate objects, so is between them gives answers that depend on how the interpreter chose to store them.

The None you did not expect

outcome = print("saved")
print(outcome)

Output:

saved
None

print() displays its arguments and then hands back None, because it has nothing else to give. Storing that result and treating it as text leads to a TypeError one line later. Any time a variable unexpectedly prints as None, the first suspect is a call whose result was stored when only its screen output was wanted.

Do
  • Write x is None and x isn't None for missing value checks
  • Use == to compare numbers, strings and lists by value
  • Start a variable at None when its real value arrives later
Avoid
  • Compare with == None; it reads as a value check and can be redefined
  • Use is between 2 numbers or 2 strings
  • Store the result of print(); it's always None

What does this program print?

first = [1, 2]
second = [1, 2]
third = first
first += [3]
print(second == third, second is third, first is third)

The fix for a stored print() result is to keep its 2 jobs apart: put the text in the variable, then pass the variable to print() at the moment it should appear on screen.

About this lesson

This section is part of the Python Expressions: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.