Fix TypeError: cannot unpack non-iterable NoneType object in Python
a, b = ... is None. The bug is wherever that value came from, not the line that crashed.
- Your own function: some path ends without
return, or hits a barereturnon an edge case. Return a tuple on every path (mypy reportsMissing return statementif you annotate the return type). - In-place methods:
list.sort(),dict.update()and pandassort_values/dropna/drop/rename/reset_index(inplace=True)returnNone. Usesorted()or dropinplace=Trueand reassign. re.match()/re.search(): no match givesNone. Checkif m:and unpackm.groups(), never the match object itself.dict.get(): a missing key givesNone. Pass a default or used[key].
TypeError: cannot unpack non-iterable NoneType object is annoying
precisely because the crashing line usually looks fine on its own, a tuple assignment, a
for-loop, a train_test_split call. The actual bug lives one step earlier, in
whatever produced None where a real value was expected. The table below lists
every case I ran for this page, including a few look-alikes that raise a different error.
Reproduced: Each Case and the Fix That Worked
Every row was run on the versions in the header. Error text is copied from the output.
| What was run | Exact error / output | Fix that worked |
|---|---|---|
train, test = split_data(rows), function has no return | TypeError: cannot unpack non-iterable NoneType object | return train, test |
if not items: return, then first, last = first_last([]) | Same TypeError | Raise on the edge case, or return a real tuple |
lo, hi = [3, 1, 2].sort() | Same TypeError | sorted([3, 1, 2]) gives [1, 2, 3] |
a, b = {'a': 1}.update({'b': 2}) | Same TypeError | Call update() on its own line, then use the dict |
top, bottom = df.sort_values('score', inplace=True) | Same TypeError (the call returned None) | df.sort_values('score', ascending=False) without inplace |
df.fillna(0, inplace=True) and replace/where/mask/clip/ffill/bfill/interpolate | pandas 3.0.6: returns the DataFrame itself, no error. pandas 2.2.3: returns None | Do not rely on the return value either way; reassign without inplace |
Wrapper that calls train_test_split(X, y) without return | TypeError: cannot unpack non-iterable NoneType object | return train_test_split(...); shapes (120, 4) (30, 4) (120,) (30,) on iris |
X_train, X_test, y_train, y_test = train_test_split(X, test_size=0.2) | ValueError: not enough values to unpack (expected 4, got 2) | Pass X, y: two arrays in, four out |
date, level, msg = re.match(pattern, "INFO server started") | TypeError: cannot unpack non-iterable NoneType object | if m := re.match(...): date, level, msg = m.groups() |
| Same unpack when the line does match | TypeError: cannot unpack non-iterable re.Match object | Unpack m.groups(), not m |
re.match(...).groups() on a non-matching line | AttributeError: 'NoneType' object has no attribute 'groups' | Same if m: guard |
n, d, c = configs.get('logistic_regression') | TypeError: cannot unpack non-iterable NoneType object | configs.get(key, (100, None, None)), or configs[key] for a clear KeyError: 'logistic_regression' |
h, w, c = cv2.imread("missing.png") | OpenCV prints a can't open/read file warning, returns None, then the same TypeError | Check if img is None: before using it |
_, contours, hierarchy = cv2.findContours(...) (OpenCV 3 style) | Not this error. OpenCV 5.0.0 returns 2 values: ValueError: not enough values to unpack (expected 3, got 2) | contours, hierarchy = cv2.findContours(...) |
The Error, in Two Real Tracebacks
The message is always the same. These two tracebacks are copied from Python 3.12.3.
A function that never returns
def split_data(rows):
train = rows[:8]
test = rows[8:]
# forgot: return train, test
train, test = split_data(list(range(10)))
Traceback (most recent call last):
File "/tmp/unpack/tb.py", line 6, in <module>
train, test = split_data(list(range(10)))
^^^^^^^^^^^
TypeError: cannot unpack non-iterable NoneType object
A regex that did not match
Traceback (most recent call last):
File "/tmp/unpack/tb2.py", line 3, in <module>
date, level, msg = re.match(r"(\d{4}-\d{2}-\d{2}) (\w+) (.+)", line)
^^^^^^^^^^^^^^^^
TypeError: cannot unpack non-iterable NoneType object
Note where the carets point. On Python 3.12, 3.13 and 3.14 they underline the targets
(train, test), not the call on the right. The traceback does not tell you which
function returned None; you have to follow the right-hand side yourself. The
message text was identical on all three versions.
In both cases Python is trying to iterate over the right-hand side of an unpacking assignment
(a, b = <expr>) and finds None where it expected a sequence.
Python cannot iterate over None, so it raises TypeError rather than
the more familiar ValueError: not enough values to unpack.
Why a for-loop over None gives a different message
Unpacking is how Python lets you assign multiple names from a single iterable in one
statement. Any time you write one of the patterns below, Python calls iter()
internally on the right-hand side:
# Tuple unpacking: needs an iterable with exactly 2 items
a, b = some_function()
# Extended unpacking: first element + rest
first, *rest = some_function()
# For-loop body unpacking: each element must itself be iterable
for key, value in some_function():
print(key, value)
# Nested unpacking in a comprehension
pairs = [(k, v) for k, v in some_function()]
When some_function() returns None, the first two patterns raise
TypeError: cannot unpack non-iterable NoneType object. The for-loop and the
comprehension fail one step earlier, when they try to iterate over None, so the
message there is TypeError: 'NoneType' object is not iterable. The "cannot unpack"
message shows up in a loop only when one element is None:
for k, v in [(1, 2), None] raises it on the second item.
# Quick proof
x = None
a, b = x # TypeError: cannot unpack non-iterable NoneType object
for k, v in None: # TypeError: 'NoneType' object is not iterable
pass
# iter() is what Python uses internally
iter(None) # TypeError: 'NoneType' object is not iterable
In-place methods that return None
Many pandas methods return None when you pass inplace=True: the
DataFrame is modified and nothing new is handed back. On pandas 3.0.6 that is true for
sort_values, sort_index, drop,
drop_duplicates, dropna, rename,
reset_index, set_index, query and eval.
The same method without inplace=True returns a DataFrame you can unpack or
chain. See also the related pandas
SettingWithCopyWarning article for other pandas anti-patterns.
sort_values(inplace=True) inside an unpack
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie'],
'score': [88, 72, 95],
})
# sort_values with inplace=True returns None
sorted_df = df.sort_values('score', inplace=True)
# Trying to unpack None crashes immediately
top, bottom = sorted_df # TypeError: cannot unpack non-iterable NoneType object
Inside a function, the missing return does the damage:
import pandas as pd
def preprocess(df):
df.drop_duplicates(inplace=True)
df.dropna(inplace=True)
df.sort_values('score', ascending=False, inplace=True)
# No return statement: function implicitly returns None
# (the missing-return section below covers this half)
raw = pd.read_csv('scores.csv')
clean, summary = preprocess(raw) # TypeError: cannot unpack non-iterable NoneType object
The same trap applies to df.reset_index(inplace=True) and
df.rename(columns={...}, inplace=True). pandas 3.0 changed a second group:
fillna, replace, where, mask,
clip, ffill, bfill and interpolate with
inplace=True now return the DataFrame itself. On pandas 2.2.3 all of them
returned None. Code that works on one version can break on the other, which is
one more reason not to use the return value of an in-place call at all.
list.sort() and dict.update() do it too
lo, hi = [3, 1, 2].sort() # TypeError: cannot unpack non-iterable NoneType object
a, b = {'a': 1}.update({'b': 2}) # TypeError: cannot unpack non-iterable NoneType object
nums = sorted([3, 1, 2]) # [1, 2, 3]: sorted() returns a new list
Drop inplace=True and keep the result
import pandas as pd
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie'],
'score': [88, 72, 95],
})
# no inplace=True: keep the returned DataFrame
sorted_df = df.sort_values('score', ascending=False)
# sorted_df is a DataFrame now (unpacking it would give column names, not rows)
print(sorted_df.head())
# If you genuinely want in-place modification, just don't unpack
df.sort_values('score', inplace=True)
# df is now sorted; no assignment needed
inplace=True also blocks method chaining, since there is nothing to chain on.
Several steps without inplace
import pandas as pd
df = pd.read_csv('scores.csv')
# Chain operations: each method returns a new DataFrame
df_clean = (
df
.drop_duplicates()
.dropna(subset=['score'])
.sort_values('score', ascending=False)
.reset_index(drop=True)
)
print(df_clean.head())
A function with a missing return path
Python functions that reach the end of their body without hitting a return
statement implicitly return None. So does a bare return. The
caller only finds out when it tries to unpack the result.
A forgotten return, and a branch without one
import pandas as pd
from sklearn.preprocessing import StandardScaler
def prepare_features(df):
features = df[['age', 'income', 'score']].copy()
scaler = StandardScaler()
scaled = scaler.fit_transform(features)
# BUG: forgot to return scaled and scaler
# The function falls off the end and returns None
X, scaler = prepare_features(df) # TypeError: cannot unpack non-iterable NoneType object
The same pattern appears when an early conditional path forgets its return but a later one does not:
def load_data(path, mode='train'):
if mode == 'train':
df = pd.read_csv(path)
X = df.drop('label', axis=1)
y = df['label']
return X, y
elif mode == 'test':
df = pd.read_csv(path)
X = df.drop('label', axis=1)
# BUG: no return in this branch, so the call returns None
y = df['label']
X_test, y_test = load_data('test.csv', mode='test')
# TypeError: cannot unpack non-iterable NoneType object
Return on every path, and let mypy check it
import pandas as pd
from sklearn.preprocessing import StandardScaler
def prepare_features(df):
features = df[['age', 'income', 'score']].copy()
scaler = StandardScaler()
scaled = scaler.fit_transform(features)
return scaled, scaler
X, scaler = prepare_features(df)
print(X.shape)
def load_data(path, mode='train'):
df = pd.read_csv(path)
X = df.drop('label', axis=1)
y = df['label']
return X, y # both modes did the same work, so one return covers them
X_test, y_test = load_data('test.csv', mode='test')
A quick way to catch this early: add a return type such as
-> tuple[list[int], list[int]] to functions you intend to unpack and run a type
checker. On the buggy load_data above, mypy 2.3.1 reported
error: Missing return statement [return], and on a bare return
inside such a function it reported error: Return value expected [return-value].
import pandas as pd
from sklearn.preprocessing import StandardScaler
import numpy as np
def prepare_features(df: pd.DataFrame) -> tuple[np.ndarray, StandardScaler]:
features = df[['age', 'income', 'score']].copy()
scaler = StandardScaler()
scaled = scaler.fit_transform(features)
return scaled, scaler # mypy enforces this return is present
A train_test_split helper that returns nothing
sklearn.model_selection.train_test_split always returns twice as many
arrays as you pass in. Pass two arrays, get four back. Pass three arrays, get six back.
Getting the count wrong raises ValueError, not this error. The
NoneType version comes from a helper function that calls
train_test_split and never returns the result.
Splits assigned to one name, and a helper with no return
from sklearn.model_selection import train_test_split
import pandas as pd
df = pd.read_csv('housing.csv')
X = df.drop('price', axis=1)
y = df['price']
# splits is a list of 4 objects, so this is a ValueError, not the NoneType error
splits = train_test_split(X, y, test_size=0.2, random_state=42)
# Caller then tries to unpack wrong number of items
X_train, y_train = splits # ValueError: too many values to unpack (expected 2)
# The NoneType version: a wrapper that forgets to return
def make_splits(X, y):
train_test_split(X, y, test_size=0.2, random_state=42)
# BUG: no return
result = make_splits(X, y)
X_train, X_test, y_train, y_test = result
# TypeError: cannot unpack non-iterable NoneType object
Wrong number of names is a ValueError instead
from sklearn.model_selection import train_test_split
# Passing one array but expecting four names is a mismatch
X_train, X_test, y_train, y_test = train_test_split(X, test_size=0.2)
# ValueError: not enough values to unpack (expected 4, got 2)
# Passing two arrays but only three names
X_train, X_test, y_train = train_test_split(X, y, test_size=0.2)
# ValueError: too many values to unpack (expected 3)
# (Python 3.14 adds the count: "expected 3, got 4")
Four names for two arrays, and return from helpers
from sklearn.model_selection import train_test_split
import pandas as pd
df = pd.read_csv('housing.csv')
X = df.drop('price', axis=1)
y = df['price']
# 2 arrays in, 4 out
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
)
print(X_train.shape, X_test.shape)
print(y_train.shape, y_test.shape)
# If you use a helper function, always return the result
def make_splits(X, y, test_size=0.2, seed=42):
return train_test_split(X, y, test_size=test_size, random_state=seed)
X_train, X_test, y_train, y_test = make_splits(X, y)
Count the names you need before you write the call: it's always twice the number of arrays going in, so two arrays in means four names out, three arrays means six. If your features and targets both have missing values that cause issues downstream, see ValueError: NaN in sklearn estimators.
re.match() and re.search() finding nothing
re.match() and re.search() return a match object on
success, or None when the pattern does not match. If you call
.groups() or try to unpack the match object directly without checking for
None first, you get the error.
Log lines that do not fit the pattern
import re
log_line = "2026-07-16 ERROR connection refused"
# The pattern expects this exact format. Other lines return None.
match = re.match(r'(\d{4}-\d{2}-\d{2}) (\w+) (.+)', log_line)
# Unpacking match.groups() works here because the line matches.
date, level, message = match.groups()
# If match were None, this line raises
# AttributeError: 'NoneType' object has no attribute 'groups'
# Another common form: unpack the match itself (not .groups())
date, level, message = re.match(r'(\d{4}-\d{2}-\d{2}) (\w+) (.+)', log_line)
# Fails even when the line matches:
# TypeError: cannot unpack non-iterable re.Match object
# and on a non-matching line:
# TypeError: cannot unpack non-iterable NoneType object
import re
# Simulating a line that doesn't match
log_line = "INFO server started"
match = re.match(r'(\d{4}-\d{2}-\d{2}) (\w+) (.+)', log_line)
print(match) # None
date, level, message = match.groups()
# AttributeError: 'NoneType' object has no attribute 'groups'
# (TypeError: cannot unpack non-iterable NoneType object if you unpack match directly)
Check the match before calling .groups()
import re
def parse_log_line(line: str):
pattern = r'(\d{4}-\d{2}-\d{2}) (\w+) (.+)'
match = re.match(pattern, line)
if match is None:
# Return a sentinel or raise a meaningful error
return None, None, None
date, level, message = match.groups()
return date, level, message
log_line = "2026-07-16 ERROR connection refused"
date, level, message = parse_log_line(log_line)
if date is not None:
print(f"Date={date} Level={level} Message={message}")
import re
# Walrus operator (Python 3.8+) for compact inline guard
log_line = "2026-07-16 WARNING disk usage above 90%"
if m := re.match(r'(\d{4}-\d{2}-\d{2}) (\w+) (.+)', log_line):
date, level, message = m.groups()
print(date, level, message)
else:
print("Line did not match expected format:", repr(log_line))
import re
# Processing a list of log lines safely
lines = [
"2026-07-16 ERROR connection refused",
"INFO server started", # won't match
"2026-07-15 DEBUG cache warm",
]
pattern = re.compile(r'(\d{4}-\d{2}-\d{2}) (\w+) (.+)')
parsed = []
for line in lines:
m = pattern.match(line)
if m:
date, level, message = m.groups()
parsed.append({'date': date, 'level': level, 'message': message})
else:
print(f"Skipping unmatched line: {repr(line)}")
print(parsed)
Note that re.match() only matches at the start of the string, while
re.search() finds a match anywhere. Both return None on failure,
so both need the same guard.
dict.get() on a missing key
dict.get(key) returns None when the key is absent (rather than
raising KeyError). If the value stored under that key is itself expected to be
a tuple or list that you want to unpack, the silent None propagates to the
unpacking site and crashes there.
A missing model name, and a nested .get()
model_configs = {
'random_forest': (100, 5, 'gini'),
'gradient_boost': (200, 3, 'friedman_mse'),
}
# Key exists: works fine
n_estimators, max_depth, criterion = model_configs['random_forest']
# Key absent: dict.get() returns None silently
params = model_configs.get('logistic_regression')
print(params) # None
n_estimators, max_depth, criterion = params
# TypeError: cannot unpack non-iterable NoneType object
# Another common variant: chained .get() on nested dicts
config = {
'preprocessing': {
'scaler': 'standard',
}
}
# Inner key missing: returns None, then None is unpacked
train_cols, test_cols = config.get('features', {}).get('columns')
# TypeError: cannot unpack non-iterable NoneType object
Give dict.get() a default
model_configs = {
'random_forest': (100, 5, 'gini'),
'gradient_boost': (200, 3, 'friedman_mse'),
}
# Provide a default tuple that matches the expected structure
params = model_configs.get('logistic_regression', (100, None, None))
n_estimators, max_depth, criterion = params
print(n_estimators, max_depth, criterion) # 100 None None
Or fail with a useful message
model_name = 'logistic_regression'
params = model_configs.get(model_name)
if params is None:
raise KeyError(
f"No config found for model '{model_name}'. "
f"Available: {list(model_configs.keys())}"
)
n_estimators, max_depth, criterion = params
Or use d[key] when the key must exist
# If the key is supposed to always be present, use [] not .get()
# This raises KeyError immediately with the missing key name,
# which is much easier to debug than the unpacking TypeError.
try:
n_estimators, max_depth, criterion = model_configs['logistic_regression']
except KeyError as exc:
print(f"Missing model config: {exc}")
raise
dict.get() is the right call when a missing key is a normal, expected outcome.
When it isn't, when the program genuinely can't proceed without that key, reach for
dict[key] instead so the failure shows up as a KeyError right where
it happened, not three lines later as a confusing unpacking crash.
Finding the None: print type() before the unpack
When you see TypeError: cannot unpack non-iterable NoneType object and the cause
is not immediately obvious, insert a print(type(x)) (or
print(repr(x))) on the line immediately before the unpacking assignment. This
confirms whether the value is None and helps you trace back to where it was set.
import pandas as pd
def get_top_and_bottom(df, col, n=3):
if df.empty:
return # BUG: edge case returns None
df_sorted = df.sort_values(col, ascending=False)
return df_sorted.head(n), df_sorted.tail(n)
df = pd.DataFrame({'score': [88, 72, 95, 60]})
top, bottom = get_top_and_bottom(df, 'score', n=2) # fine: [95, 88] and [72, 60]
result = get_top_and_bottom(df[df['score'] > 100], 'score') # empty after filtering
# Diagnostic: add this before unpacking
print(type(result)) # <class 'NoneType'> <-- confirms it is None
print(repr(result)) # None
top, bottom = result # TypeError (now expected: trace back to the function)
Here the function is correct for normal data and returns None only when a filter leaves nothing. Decide what an
empty input should produce (raise, or return two empty frames) instead of a bare
return.
# More precise diagnostic: check every variable in a pipeline
import pandas as pd
from sklearn.model_selection import train_test_split
def run_pipeline(path):
df = pd.read_csv(path)
X = df.drop('target', axis=1)
y = df['target']
splits = train_test_split(X, y, test_size=0.2)
# forgot to return
print(f"splits type: {type(splits)}") # <class 'list'>, never returned
result = run_pipeline('data.csv')
print(f"result type: {type(result)}") # NoneType: function returned None
X_train, X_test, y_train, y_test = result
# TypeError: cannot unpack non-iterable NoneType object
Once you've confirmed the value really is None, walk the traceback back to
wherever that variable was last assigned. That's where the actual bug lives, not at the
unpacking line where it happened to blow up.
You can also use Python's built-in assert to catch this early in development:
result = some_function()
assert result is not None, f"some_function() returned None: check its return statements"
a, b = result
When None is a normal result
When a None result is legitimate (no match, no config, empty input), check for
it before you unpack.
result = function_that_might_return_none()
# None means a bug upstream: raise with a clear message
if result is None:
raise ValueError(
"function_that_might_return_none() returned None. "
"Check that it has a return statement and that all "
"code paths return a value."
)
a, b = result
# None means "nothing configured": fall back to a default
if result is None:
result = (default_a, default_b)
a, b = result
# None means "skip this item"
items = [function_that_might_return_none(x) for x in data]
for item in items:
if item is None:
continue
a, b = item
process(a, b)
# Same check with the walrus operator (Python 3.8+)
if result := function_that_might_return_none():
a, b = result
else:
print("No result: skipping")
If your traceback matches none of these, use the print(type(x)) check and follow
the right-hand side back to whatever returned None.
For other common data-pipeline errors in the same stack, see pandas datetime parsing errors and ValueError: NaN in sklearn estimators.