"
This article is part of in the series
Last Updated: Saturday 30th September 2023

Python codes on screen

What is the null or None Keyword

The null keyword is commonly used in many programming languages, such as Java, C++, C# and Javascript. It is a value that is assigned to a variable. Perhaps you have seen something like this:

null in Javascript

[javascript]
var null_variable = null;
[/javascript]

null in PHP

[php]
$null_variable = NULL;
[/php]

null in Java

[java]
SomeObject null_object = null;
[/java]

The concept of a null keyword is that it gives a variable a neutral, or "null" behaviour. Note that technically the behaviour of null changes between higher and lower-level languages, so to keeps things simple we'll be referring to the concept in object-orientated languages.

Python's null Equivalent: None

The equivalent of the null keyword in Python is None. It was designed this way for two reasons:

  • Many would argue that the word "null" is somewhat esoteric. It's not exactly the most friendliest word to programming novices. Also, "None" refers exactly to the intended functionality - it is nothing, and has no behaviour.
  • In most object-oriented languages, the naming of objects tend to use camel-case syntax. eg. ThisIsMyObject. As you'll see soon, Python's None type is an object, and behaves as one.

The syntax to assign the None type to a variable, is very simple. As follows:

[python]
my_none_variable = None
[/python]

Why Use Python's None Type?

There are many cases on why you would use None.

Often you will want to perform an action that may or may not work. Using None is one way you can check the state of the action later. Here's an example:

[python]
# We'd like to connect to a database. We don't know if the authentication
# details are correct, so we'll try. If the database connection fails,
# it will throw an exception. Note that MyDatabase and DatabaseException
# are not real classes, we're just using them as examples.

database_connection = None

# Try to connect
try:
database = MyDatabase(db_host, db_user, db_password, db_database)
database_connection = database.connect()
except DatabaseException:
pass

if database_connection is None:
print('The database could not connect')
else:
print('The database could connect')
[/python]

Another scenario would be where you may need to instantiate a class, depending on a condition. You could assign a variable to None, then optionally later assign it to an object instance. Then after that you may need to check if the class has been instantiated. There are countless examples - feel free to provide some in the comments!

Python's None is Object-Orientated

Python is very object-orientated, and you'll soon see why. Note that the Null keyword is an object, and behaves as one. If we check what type the None object is, we get the following:



[python]
>>> type(None)
<class 'NoneType'>
[/python]


[python]
>>> type(None)
<type 'NoneType'>
[/python]

We can discover three things from this:

  • None is an object - a class. Not a basic type, such as digits, or True and False.
  • In Python 3.x, the type object was changed to new style classes. However the behaviour of None is the same.
  • Because None is an object, we cannot use it to check if a variable exists. It is a value/object, not an operator used to check a condition.

It's interesting to note that in Python, there's only one None object, and as you might have guessed, its type is NoneType. This means when you encounter None anywhere in your code, it's always the same object.

What's more, 'NoneType' is immutable, just like the strings in Python. This means once the None object is created, it cannot be modified. And if you haven't noticed it yet, note that you cannot perform operations directly on None.

Checking if a Variable is None

There are two ways to check if a variable is None. One way can be performed by using the is keyword. Another is using the == syntax. Both comparison methods are different, and you'll see why later:

[python]
null_variable = None
not_null_variable = 'Hello There!'

# The is keyword
if null_variable is None:
print('null_variable is None')
else:
print('null_variable is not None')

if not_null_variable is None:
print('not_null_variable is None')
else:
print('not_null_variable is not None')

# The == operator
if null_variable == None:
print('null_variable is None')
else:
print('null_variable is not None')

if not_null_variable == None:
print('not_null_variable is None')
else:
print('not_null_variable is not None')
[/python]

This code will give us the following output:
[shell]
null_variable is None
not_null_variable is not None
null_variable is None
not_null_variable is not None
[/shell]

Great, so they're the same! Well, sort of. With basic types they are. However with classes you need to be careful. Python provides classes/objects with the ability to override comparison operators. So you can compare classes, for example MyObject == MyOtherObject. This article won't go into depth on how to override comparison operators in classes, but it should provide insight as to why you should avoid checking if a variable is None using the == syntax.

[python]
class MyClass:
def __eq__(self, my_object):
# We won't bother checking if my_object is actually equal
# to this class, we'll lie and return True. This may occur
# when there is a bug in the comparison class.

return True

my_class = MyClass()

if my_class is None:
print('my_class is None, using the is keyword')
else:
print('my_class is not None, using the is keyword')

if my_class == None:
print('my_class is None, using the == syntax')
else:
print('my_class is not None, using the == syntax')
[/python]

That gives us the following output:

[shell]
my_class is not None, using the is keyword
my_class is None, using the == syntax
[/shell]

Interesting! So you can see that the is keyword checks if two objects are exactly the same. Whereas the == operator first checks if the class has overridden the operator. For the PHP coders out there, using the == syntax is the same as == in Python, where using the is keyword is equivalent to the === syntax.

Because of this, it's always advisable to use the is keyword to check if two variables are exactly the same.

Avoiding Mutable Default Parameters with None

In Python, mutable objects like lists or dictionaries sometimes display unintended behavior when used as default parameters in functions. The underlying problem stems from the way Python handles function definitions.

Consider the following function as an example:

def problematic_function(element, initial_list=[]):

    initial_list.append(element)

    return initial_list

 

On the surface, it's hard to see anything wrong with this function. Even if you use an already initialized list, here's what happens:

existing_list = ['x', 'y', 'z']

print(problematic_function('w', existing_list))  

# Outputs: ['x', 'y', 'z', 'w']

 

As you can see, the code adds 'w' to existing_list without any issues. But, when you call the function multiple times without specifying the initial_list parameter, you will see unexpected results like this:

print(problematic_function('x'))  

# Outputs: ['x']

print(problematic_function('y'))  

# Outputs: ['x', 'y']

print(problematic_function('z'))  

# Outputs: ['x', 'y', 'z']

 

This happens because the mutable default arguments in Python are created only once – when the function is defined. So, the same list object gets reused for subsequent calls when a list is not passed explicitly. 

The solution? Most developers resort to using "None" as the default parameter value when declaring the function. It's also essential to check for the "None" value inside the function. This way, you can initialize a new mutable object like a list if the value is present.

Let's rewrite the function taking this approach: 

def improved_function(element, initial_list=None):

    if initial_list is None:

        initial_list = []

    initial_list.append(element)

    return initial_list

 

Let's see what happens if we try calling this function without specifying the initial_list parameter:

print(improved_function('a', existing_list))  # Outputs: ['x', 'y', 'z', 'w', 'a']

print(improved_function('x'))  # Outputs: ['x']

print(improved_function('y'))  # Outputs: ['y']

print(improved_function('z'))  # Outputs: ['z']

 

As you can see, this new function ensures that a fresh list is created for each call where the initial_list parameter isn't provided. 

Taking this approach avoids the potential pitfalls associated with mutable default parameters in Python, making the code more predictable and safer to run.Â