Skip to content

Fix TypeError: cannot unpack non-iterable NoneType object in Python

Tested with: Python 3.12.3 (tracebacks also checked on 3.13.13 and 3.14.4), pandas 3.0.6 (and 2.2.3 for comparison), scikit-learn 1.9.1, numpy 2.5.3, opencv-python-headless 5.0.0, mypy 2.3.1, Linux. Last run 2026-09-27.

TL;DR: the right-hand side of a, b = ... is None. The bug is wherever that value came from, not the line that crashed.
  1. Your own function: some path ends without return, or hits a bare return on an edge case. Return a tuple on every path (mypy reports Missing return statement if you annotate the return type).
  2. In-place methods: list.sort(), dict.update() and pandas sort_values/dropna/drop/rename/reset_index(inplace=True) return None. Use sorted() or drop inplace=True and reassign.
  3. re.match() / re.search(): no match gives None. Check if m: and unpack m.groups(), never the match object itself.
  4. dict.get(): a missing key gives None. Pass a default or use d[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 runExact error / outputFix that worked
train, test = split_data(rows), function has no returnTypeError: cannot unpack non-iterable NoneType objectreturn train, test
if not items: return, then first, last = first_last([])Same TypeErrorRaise on the edge case, or return a real tuple
lo, hi = [3, 1, 2].sort()Same TypeErrorsorted([3, 1, 2]) gives [1, 2, 3]
a, b = {'a': 1}.update({'b': 2})Same TypeErrorCall 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/interpolatepandas 3.0.6: returns the DataFrame itself, no error. pandas 2.2.3: returns NoneDo not rely on the return value either way; reassign without inplace
Wrapper that calls train_test_split(X, y) without returnTypeError: cannot unpack non-iterable NoneType objectreturn 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 objectif m := re.match(...): date, level, msg = m.groups()
Same unpack when the line does matchTypeError: cannot unpack non-iterable re.Match objectUnpack m.groups(), not m
re.match(...).groups() on a non-matching lineAttributeError: 'NoneType' object has no attribute 'groups'Same if m: guard
n, d, c = configs.get('logistic_regression')TypeError: cannot unpack non-iterable NoneType objectconfigs.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 TypeErrorCheck 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.