The short answer: use a list comprehension that tests is not None.
items = [1, None, 2, None, 3, 4, None, 5]
clean = [x for x in items if x is not None]
print(clean)
[1, 2, 3, 4, 5]
That creates a new list and leaves items unchanged. The rest of this post covers the cases where you want something different: changing the list in place, removing every “empty” value, handling nested data, and avoiding the two or three bugs that catch people out.
Why is not None and not != None
None is a singleton: there is exactly one None object in a Python program. The identity test x is None asks “is this that object”, which is what you mean, and it cannot be fooled by a class that overrides ==. PEP 8 recommends is and is not for comparisons with None.
The methods
1. List comprehension (default choice)
clean = [x for x in items if x is not None]
Readable, explicit about what is being removed, and fast. Use this unless you have a specific reason not to.
2. filter(None, items) (fastest, but read the warning)
clean = list(filter(None, items))
Passing None as the function means “keep items that are truthy”. That removes None, but also 0, 0.0, False, empty strings and empty containers:
mixed = [0, None, "", 1, False, [], 2, 0.0]
print(list(filter(None, mixed)))
print([x for x in mixed if x is not None])
[1, 2]
[0, '', 1, False, [], 2, 0.0]
This is the most common bug with this idiom. If the list can contain zeros or empty strings that are valid data (prices, counts, optional text), use the comprehension. Use filter(None, ...) only when you really want to drop every falsy value.
3. filter() with a lambda
clean = list(filter(lambda x: x is not None, items))
Correct, but slower than the comprehension because it calls a Python function for every element (see the timings below), and it is harder to read. There is no good reason to prefer it, except that it composes well if you already have a named predicate function to pass in.
4. A plain for loop
clean = []
for x in items:
if x is not None:
clean.append(x)
Use it when the loop body does more than filtering, for example logging each skipped value or converting the kept ones.
5. In place: slice assignment
If other code holds a reference to the same list and must see the change, rebinding a variable is not enough. Assign to the whole slice instead:
a = [1, None, 2, None, 3]
b = a # another reference to the same list
a[:] = [x for x in a if x is not None]
print(a, b)
[1, 2, 3] [1, 2, 3]
Compare with plain rebinding, where b keeps the old contents:
a = [1, None, 2]
b = a
a = [x for x in a if x is not None]
print(a, b)
[1, 2] [1, None, 2]
This is the usual situation with function arguments and object attributes: a function that receives a list and is supposed to clean it for its caller has to use lst[:] = ... (or return the new list and let the caller reassign).
6. Repeated remove(None) (avoid)
while None in items:
items.remove(None)
It works, and it modifies the list in place, but each None in items check and each remove scans the list from the start. With k Nones in a list of n items that is roughly n × k work, so it gets slow quickly. The timings below show how slow.
Timings
I measured each approach on a list of 100,000 integers where every tenth element is None, using Python 3.12 and averaging 20 runs (your numbers will differ, the ratios are what matter):
| Method | Time |
|---|---|
filter(None, items) |
0.70 ms |
| List comprehension | 1.05 ms |
For loop with append |
1.31 ms |
| Slice assignment from a comprehension (on a copy) | 1.76 ms |
filter with a lambda |
3.35 ms |
itertools.filterfalse with a lambda |
3.14 ms |
for _ in range(items.count(None)): items.remove(None) |
3,413 ms |
The remove approach is more than 3,000 times slower on this input. On a 20,000-item list, the while None in ... loop took about 270 ms against 0.21 ms for the comprehension. For lists of a few dozen items none of this matters, so choose for readability; but the gap is why remove in a loop is worth avoiding.
Common mistakes
Removing while iterating
bad = [None, None, 1, None]
for x in bad:
if x is None:
bad.remove(x)
print(bad)
[1, None]
Two None values are still there. When you remove an element, the items after it shift left, and the loop’s internal index then skips the element that moved into the removed slot. Never change a list’s length while looping over it. Build a new list (comprehension) or loop over a copy.
Forgetting that filter returns an iterator
In Python 3, filter(...) returns a lazy iterator, not a list. len(filter(None, items)) raises a TypeError, and iterating it twice gives nothing the second time. Wrap it in list() if you need a list.
Treating NaN like None
A missing value from NumPy or pandas is often float('nan'), not None, and nan is not None:
import math
nan = float("nan")
print([x for x in [1.0, nan, 2.0] if x is not None])
print([x for x in [1.0, nan, 2.0] if not math.isnan(x)])
[1.0, nan, 2.0]
[1.0, 2.0]
(math.isnan only works on numbers, so use it when the list is numeric.)
Dropping None and misaligning parallel lists
If you have two lists matched by position, removing None from only one breaks the pairing. Filter them together:
names = ["a", "b", "c"]
vals = [1, None, 3]
print([(n, v) for n, v in zip(names, vals) if v is not None])
[('a', 1), ('c', 3)]
Other situations
Nested lists
A flat comprehension only looks at the top level. For arbitrarily nested lists, recurse:
def strip_none(v):
if isinstance(v, list):
return [strip_none(x) for x in v if x is not None]
return v
print(strip_none([1, [2, None, 3], [4, [5, None]], None, 6]))
[1, [2, 3], [4, [5]], 6]
This only descends into lists. If your data can contain tuples or dicts, add cases for them.
Dictionaries
d = {"a": 1, "b": None, "c": 0}
print({k: v for k, v in d.items() if v is not None})
{'a': 1, 'c': 0}
Replace instead of remove
Sometimes the position matters and you want a default instead of deleting the item:
print([0 if x is None else x for x in items])
[1, 0, 2, 0, 3, 4, 0, 5]
Just computing something from the values
You do not always need a cleaned list. A generator expression skips the Nones without building anything:
print(sum(x for x in items if x is not None))
15
This is often why the question comes up: sum([1, None]) and sorted([3, None, 1]) both raise TypeError, because Python cannot add or compare None with a number.
pandas
For a pandas Series or DataFrame, use dropna() rather than converting to a list: pd.Series([1, None, 3]).dropna() keeps the values 1.0 and 3.0 (the integers become floats because pandas stores the missing value as NaN).
Which one should you use?
- Default:
[x for x in items if x is not None]. - Must update the list that others reference:
items[:] = [x for x in items if x is not None]. - Want to drop every falsy value on purpose:
list(filter(None, items)). - Need to log, convert or count while filtering: a for loop.
- Avoid:
remove(None)in a loop, and modifying a list while iterating.
If you are still getting comfortable with lists and loops, the AlgoCademy lessons on looping over lists and removing items from a list let you practice in the browser. The same idea of building a filtered copy shows up in the JavaScript filter method.
