The None Datatype

Python is a dynamic-typed language, which means that it is not necessary to indicate the type of a variable when creating it, nor is mandatory for a variable to keep its datatype during the program execution. A variable might be created as an integer, then changed to a string, then to a floating point, etc. However, every variable in a program has some datatype (or, more precisely, every variable is an instance of some class, since everything in Python is an object) which can be known via the type() built-in function:

>>> pi = 3.14
>>> type(pi)
<class 'float'>

The four basic datatypes are the integer number (int), the floating-point number (float), the string (str), and the boolean (bool). But Python includes a fifth datatype called NoneType, and whose only possible value is None.

>>> a = None
>>> type(a)
<class 'NoneType'>

None is often used when we want to create a variable (since Python doesn't distinguish creation from assignation, to create a variable is simply to give a value), but yet not to assign a value to it. Note that None should not be considered the same as NULL in languages like C and C++, where it is related to pointers. In Python, None can be assigned to any object.

# A variable can be created with a 'None' and later
# re-assigned to another value.
>>> a = None
>>> type(a)
<class 'NoneType'>
>>> a = "Hello, world!"
>>> type(a)
<class 'str'>
# Or vice-versa.
>>> b = 5
>>> b = None

When we type the name of a variable whose content is None in the interactive shell, nothing is printed.

>>> a = None
>>> a
>>>

Since we are treating with an ordinary datatype (although limited to a single value), rather than with some kind of special object, we can perform the usual comparisons:

a = None
if a == None:
     print("a is empty.")
else:
     print("a is not empty.")

Although this code is valid, the recommended way of comparing this datatype is using the is keyword:

a = None
# Recommended way.
if a is None:
     print("a is empty.")
else:
     print("a is not empty.")

And also for negative comparisons:

a = 1
b = 2
# Valid.
if not a is None:
     print("a is not empty.")
# Valid and more readable.
if b is not None:
     print("b is not empty.")

Why should we use is instead of == when comparing against None? The reason behind this is that there is actually only one None object stored in memory, no matter how many variables are in our program whose value is None. When we assign None to a variable, Python will just point our variable to that already-stored None value. So you can think about None-variables as pointers all pointing to the same object. This can be tested and confirmed by using the id() built-in function, which returns the memory address of the object passed as argument:

>>> a = None
>>> b = None
>>> id(a)
1636093900
>>> id(b)
1636093900

a and b, and any other variable containing None, are just different names for the same object. Because of this we use is when comparing against None, since the is keyword compares the memory address of two objects, not their values. The equality (==) and inequality (!=) operators actually just call the __eq__() magic method, which might be overrided to return True when comparing against None, even if the object is not None:

class MySpecialObject:
     def __eq__(self, obj):
         if obj is None:
             return True
         return super().__eq__(obj)
special_object = MySpecialObject()
print(special_object == None)  # True
print(special_object is None)  # False

But this is somewhat advanced Python code, just remember to use is when comparing against None.

Usage example

When is it useful to use None? We already said that it is specially useful when we want to create a variable but do not assign a value to it yet. For example, the following code searches for the "Python" string within a list of programming languages and prints its position, if found:

languages = ["C", "C++", "Python", "Java", "Go"]
position = None
for i, element in enumerate(languages):
     if element == "Python":
         position = i
         break
if position is None:
     print("Element not found.")
else:
     print("Element found in position:", i)

Here we initially define position as None, since it might happen that the element we are looking for is not in the list. If position is still None after the execution of the for loop, that means the element was not found. The initial value of position could not have been zero, since zero is a valid index when working with lists (zero is the first element). Neither -1, since list elements might be negative numbers also: -1 is the last element, -2 the previous to the last element, etc. So None fits perfectly well for this task.

None is also specially useful when defining default values for arguments. Let's suppose we have a function that takes a list as its first argument and prints out each element:

def print_list(my_list):
     for element in my_list:
         print(element)
print_list([1, 2, 3, 4])

Now, we want our function to print out the lines of a file when the user doesn't pass a value to the first argument. By setting None as the default value, we can know from inside the function where the argument was passed or not:

def print_list(my_list=None):
     if my_list is not None:
         for element in my_list:
             print(element)
     else:
         with open("my_file.txt") as f:
             print(f.read())
print_list([1, 2, 3, 4])  # Prints the passed list.
print_list()  # Prints the file content.