pandas-dev/pandas · error · ValueError
Value must be a positive integer or None
Error message
Value must be a positive integer or None
What it means
ValueError raised by _is_positive_int_or_none() in pandas/core/config_init.py:478, the validator registered for the 'mode.max_threads' option. The option caps worker threads for parallel operations such as read_csv on large files; legal values are None (auto) or a positive int (>=1). Passing 0, a negative number, a float, a bool-as-int surprise, or a string raises this error at option-set time.
Source
Thrown at pandas/core/config_init.py:478
max_threads_doc = """
: int or None
Maximum number of worker threads for parallel operations (e.g. ``read_csv``
for large files). ``None`` (the default) means use ``min(os.cpu_count(), 4)``,
further limited to the CPUs available to the process (CPU affinity and cgroup
limits); on Windows the default is ``1``, as parallel reading is not faster
there. Set to ``1`` to disable parallel execution, or to a fixed number to
raise the cap or to limit thread usage when pandas is embedded in a larger
parallel workflow. Ignored on Emscripten/Pyodide, which cannot spawn threads.
"""
def _is_positive_int_or_none(value: Any) -> None:
if value is None:
return
if isinstance(value, int) and value >= 1:
return
raise ValueError("Value must be a positive integer or None")
with cf.config_prefix("mode"):
cf.register_option(
"max_threads",
None,
max_threads_doc,
validator=_is_positive_int_or_none,
)
string_storage_doc = """
: string
The default storage for StringDtype.
"""
def is_valid_string_storage(value: Any) -> None:View on GitHub (pinned to 71959b8cb9)
Solutions
- Pass None to disable the cap (auto) or 1 to force single-threaded execution.
- Coerce config-sourced values explicitly: int(value) and validate value >= 1 before calling set_option, falling back to None on parse failure.
- When reading from os.environ, wrap in try/except and default to None: n = int(os.environ['PD_MAX_THREADS']) if set and int-parseable and >=1 else None.
- Avoid bools: remember True == 1 passes isinstance int but conveys the wrong intent; pass an explicit int.
Example fix
// before
pd.set_option('mode.max_threads', threads_from_config) # '0' or 4.0 -> ValueError
// after
raw = threads_from_config
n = None if raw in (None, '', 'auto') else int(raw)
if n is not None and n < 1:
raise ValueError(f'invalid thread count {raw!r}')
pd.set_option('mode.max_threads', n) Defensive patterns
Strategy: validation
Validate before calling
from typing import Any
def coerce_max_threads(value: Any):
"""Return a value safe for pd.set_option('mode.max_threads', ...) or raise."""
if value is None or value in ('', 'auto', 'none'):
return None
if isinstance(value, bool) or not isinstance(value, int):
if isinstance(value, str):
value = int(value.strip())
elif isinstance(value, float) and value.is_integer():
value = int(value)
else:
raise ValueError(f'invalid max_threads: {value!r}')
if value < 1:
raise ValueError(f'max_threads must be >= 1 or None, got {value}')
return value
import pandas as pd
pd.set_option('mode.max_threads', coerce_max_threads(config_value)) Type guard
def is_positive_int_or_none(value) -> bool:
return value is None or (isinstance(value, int) and not isinstance(value, bool) and value >= 1) Try / catch
import pandas as pd
try:
pd.set_option('mode.max_threads', raw)
except ValueError as e:
if 'positive integer or None' in str(e):
# fall back to auto
pd.set_option('mode.max_threads', None)
else:
raise Prevention
- Always coerce env/config strings to int and bounds-check >= 1 before set_option.
- Use None for 'auto' and 1 for 'single-threaded'; never 0 or negative.
- Reject bools explicitly (isinstance(x, bool)) since True/False are ints in Python.
- Centralize option setting in a helper so the coercion lives in one place.
When it happens
Trigger: pd.set_option('mode.max_threads', 0); pd.set_option('mode.max_threads', -1); pd.set_option('mode.max_threads', 4.0) (float not int); pd.set_option('mode.max_threads', '4') (string); pd.option_context('mode.max_threads', value) with a value read from an environment variable/config file without coercion; math.floor()/division producing a float that is then passed in.
Common situations: Reading thread counts from env vars or YAML config and passing the raw string; computing CPU counts with operations that yield floats; disabling parallelism by passing 0 instead of 1 or None; CI configs that set the option to a placeholder like '' or 'auto'; version upgrades where the option was renamed or its validation tightened.
Related errors
- No such keys(s): {pat!r}
- Value must be one of python|pyarrow
- Pattern matched multiple keys
- {k} is not a valid identifier
- {k} is a python keyword
AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07).
Data as JSON: /api/errors/64f46c84bb76128a.
Report an issue: GitHub.