Use is None. The difference between is None vs == None in Python is that is checks whether a name points to the one and only None object, while == asks the object’s __eq__ method, which a class can rewrite to say anything:
result is None # identity: always correct
result == None # equality: asks __eq__, can be fooled
I ran these snippets on Python 3.12.5, NumPy 2.5.3 and pandas 3.0.6; outputs and screenshots are real.
How is None and == None compare on plain values
On built-in values, both checks agree, which is why == None still shows up in so much code:
order_id = None
discount = 0
print(order_id is None, order_id == None)
print(discount is None, discount == None)
backup = None
print(backup is order_id)
Output:
True True
False False
True

None and on 0, and both names point to one object.None is a singleton: every None in your program is the same object, so an identity check asks exactly the right question.
For the full how-to, see checking if a variable is None in Python, and the difference between is and == in Python covers identity beyond None.
Why can == None give the wrong answer with __eq__?
x == None calls x.__eq__(None), so the class decides the result. Here’s a standard-library object that claims to equal None and a custom class that crashes:
from unittest.mock import ANY
print("ANY == None:", ANY == None)
print("ANY is None:", ANY is None)
class Order:
def __init__(self, total):
self.total = total
def __eq__(self, other):
return self.total == other.total
order = Order(149.99)
try:
print(order == None)
except AttributeError as err:
print("order == None failed:", err)
print("order is None:", order is None)
Output:
ANY == None: True
ANY is None: False
order == None failed: 'NoneType' object has no attribute 'total'
order is None: False

ANY == None wrongly returns True and Order.__eq__ crashes; is None is right both times.I’ve hit that second bug in real code. You can’t override is, so it never calls your method. The same goes for != None and the not equal operator in Python.
NumPy arrays and pandas Series with == None
With arrays, == compares element by element and returns an array, not one True or False, so an if fails:
import numpy as np
import pandas as pd
prices = np.array([19.99, None, 7.50], dtype=object)
print(prices == None)
try:
if prices == None:
pass
except ValueError as err:
print("ValueError:", err)
cities = pd.Series(["Austin", None, "Denver"])
print((cities == None).tolist())
print(cities.isna().tolist())
Output:
[False True False]
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
[False, False, False]
[False, True, False]

if; in pandas only isna() finds the missing city.pandas stores the missing city as a missing-value marker, so == None is False everywhere. You’ll want isna() instead. See checking for NaN in Python and dropping rows with NaN in pandas for the rest of the cleanup.
Write is not None instead of != None or not x
The negative form follows the same rule: if x is not None:. Avoid if not x: when you mean “missing”, because 0, 0.0 and an empty string are falsy too.
With if not discount:, a $0.00 discount would be treated like no discount at all. The if not condition in Python explains how falsy values behave, and checking that a variable is not null shows the pattern.
Ruff E711 flags comparison to None
PEP 8 says comparisons to singletons like None should use is or is not, and linters can enforce it.
Ruff 0.17’s default rules passed this file in my test, so you’ll need --select E711. Then it flags both lines in this function with an optional argument defaulting to None:
def apply_discount(price, coupon=None):
if coupon == None:
return price
if coupon.get("percent") != None:
return round(price * (1 - coupon["percent"] / 100), 2)
return price
Output:
checkout.py:2:18: E711 Comparison to `None` should be `cond is None`
checkout.py:4:33: E711 Comparison to `None` should be `cond is not None`
Found 2 errors.
2 unsafe fixes available with `--fix --unsafe-fixes`.

is forms.is None vs == None at a glance
| Check | Tests | Calls __eq__ | Use it for |
|---|---|---|---|
x is None | Same object | No | Every None check |
x == None | Equal value | Yes | Avoid (E711) |
x is not None | Not the same object | No | Value is present |
If you handle missing data often, you might like these:
- Check if a variable is null or empty in Python
- Remove None values from a list in Python
- The difference between = and == in Python
- Python operators explained
Frequently asked questions
Should I use is None or == None in Python?
Use is None. A class’s __eq__ method can’t change its answer.
Is is None faster than == None?
Slightly. is compares object identity directly, while == goes through the comparison methods first.
Why does x == None return True when x isn’t None?
Its __eq__ method says so, as unittest.mock.ANY does on purpose.
Is is not None the same as not x is None?
Same result, but PEP 8 recommends is not None because it reads better.
What does ruff E711 mean?
E711 means a comparison to None uses == or !=. Change it to is or is not. Enable it with --select E711.
Where are is and is not documented?
See identity comparisons in the Python language reference.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn