Python does not have null. The equivalent is None, and the difference is more than spelling. None is a real object - the single instance of the NoneType class - not the absence of a value the way null is in some languages. You test for it with is None, not == None, because is checks identity and there is exactly one None in the whole program. If you are coming from Java, JavaScript, or SQL, the mental shift is that Python’s nothing is itself a thing you can point at.
Table of contents
- None is a singleton object
- Always use is None, not == None
- None as a default, and the mutable-default trap
- None is falsy, but not the only falsy thing
- Where None comes from when you did not ask for it
- How this fits the rest of the stack
- FAQ
None is a singleton object
In many languages, null is a special non-value baked into the runtime. In Python, None is an ordinary object that happens to be unique:
type(None) # <class 'NoneType'>
None is None # True
There is exactly one None object. Every variable set to None points at the same instance. You cannot create another NoneType and you cannot reassign None - it is a keyword.
This is why is is the correct test. x is None asks are these the same object, and because there is only one None, that question is exact and fast. It is not a style preference; it is using the right operator for a singleton.
Always use is None, not == None
if x is None: # correct
...
if x == None: # works by accident, avoid
...
== calls the object’s __eq__ method, which any class can override. A custom object could define __eq__ so that obj == None returns True even when obj is clearly not None - or raise, or do something slow. is cannot be overridden; it compares identity directly.
So is None is both correct and safe against classes that lie about equality. Linters flag == None for exactly this reason. Make is None and is not None your reflex and you never have to think about it again.
None as a default, and the mutable-default trap
None is the standard placeholder for no value provided yet, especially for optional function arguments:
def connect(timeout=None):
if timeout is None:
timeout = 30 # the real default, computed here
...
This pattern exists to dodge a genuine Python footgun: mutable default arguments. A default like def f(items=[]) is evaluated once at definition time, so every call shares the same list, and it accumulates across calls. Using None as the sentinel and building the real default inside the function avoids that entirely:
def f(items=None):
if items is None:
items = []
items.append(1)
return items
If you have ever seen a function mysteriously remember values from previous calls, a mutable default was the cause, and None as the sentinel is the fix.
None is falsy, but not the only falsy thing
None evaluates as false in a boolean context, which tempts people to test it with a bare if:
if not x: # true for None, but ALSO for 0, "", [], {}, False
...
This is a bug waiting to happen. not x is true for None and for every other falsy value - zero, an empty string, an empty list. If x can legitimately be 0 or "", if not x treats those as if they were missing, which is usually wrong.
if x is None: # true ONLY for None
...
When you mean specifically None, say is None. Reserve if not x for when you genuinely mean any empty or falsy value. Conflating the two is one of the most common quiet bugs in Python code that handles optional data.
Where None comes from when you did not ask for it
None shows up as the default return of any function that does not explicitly return anything:
def log(msg):
print(msg) # no return statement
result = log("hi") # result is None
Every function returns something; without a return, that something is None. This is behind a classic mistake - calling a mutating method and assigning its result:
nums = [3, 1, 2]
nums = nums.sort() # BUG: sort() returns None, nums is now None
list.sort() sorts in place and returns None, so reassigning wipes out your list. The same trap hits .append(), .reverse(), and other in-place methods. If a variable is unexpectedly None, look for an in-place method whose return value you assigned by mistake - it is nearly always that.
How this fits the rest of the stack
Handling nothing correctly - distinguishing a missing value from a zero, from an empty string, from a function that forgot to return - is the kind of small rigor that keeps data pipelines honest. When that code becomes an API other services call, a None that should have been a real value is exactly the sort of bug that only shows up under load, where good logs earn their keep. The RunxBuild hosting calculator lays out the service, database, storage, and bandwidth as separate line items, and the RunxBuild dashboard is where the team watches deploys, logs, and restarts as they happen.
Useful related references:
- Private Methods in Python: There Are None, and That Is Fine
- API vs Web Service: The Difference That Matters When You Deploy One
- Change Python Version: The Three Tools That Make It Stop Hurting
- Python services on RunxBuild
FAQ
Is there a null in Python?
No. Python has None instead, which is a real singleton object of type NoneType, not the language-level absence of a value that null is elsewhere. Test for it with is None. Coming from Java, JavaScript, or SQL, treat None as Python’s equivalent of null.
Should I use is None or == None?
Use is None. It compares object identity, and since there is exactly one None object, the check is exact and cannot be fooled by a class that overrides __eq__. == None works by accident but is flagged by linters and can behave unexpectedly with custom objects.
Why is my variable None when I did not set it?
A function without an explicit return returns None, and in-place methods like list.sort(), .append(), and .reverse() return None too. Assigning their result - nums = nums.sort() - replaces your value with None. Look for an in-place method whose return you captured by mistake.
Why not just use if not x to check for None?
Because not x is true for every falsy value - None, 0, "", [], {}, False - not only None. If x can legitimately be zero or empty, if not x mistreats those as missing. Use if x is None when you mean specifically None.
What is the mutable default argument trap and how does None fix it?
A default like def f(items=[]) is created once and shared across all calls, so it accumulates state. Use def f(items=None) and build the real default inside the function with if items is None: items = [], so each call gets a fresh object.