pola-rs/polars · error
python function should return List[str]
Error message
python function should return List[str]
What it means
In the same with_schema_modify flow, polars extracts the lambda's return value as Vec<String>. `expect("python function should return List[str]")` panics when the callback returns something that is not a list of strings (e.g. a single string, a dict, a tuple, None, or a list containing non-string items).
Solutions
- Return a plain Python list of str: lambda names: [n.lower() for n in names].
- If you have a dict mapping, convert: lambda names: [mapping.get(n, n) for n in names].
- Convert numpy/pandas name collections: lambda names: [str(n) for n in list(names_obj)].
- Ensure every element is str (e.g. wrap with str(n) if names may include other types).
Example fix
# before
pl.scan_csv(path, with_schema_modify=lambda names: {n: n.lower() for n in names}) # dict -> panic
# after
pl.scan_csv(path, with_schema_modify=lambda names: [n.lower() for n in names]) # List[str] Defensive patterns
Strategy: type-guard
Validate before calling
result = fn(['a', 'b']) assert isinstance(result, list) and all(isinstance(x, str) for x in result)
Type guard
def is_list_of_str(v):
return isinstance(v, list) and all(isinstance(x, str) for x in v) Try / catch
try:
df = pl.read_csv(path, with_schema_modify=my_renamer)
except BaseException as e:
if 'List[str]' in str(e):
raise TypeError('with_schema_modify must return List[str]') from e
raise Prevention
- Always return a plain Python list of str from the renamer.
- Convert dicts/arrays: [mapping.get(n, n) for n in names].
- Coerce elements with str(n) if sources may be non-string.
When it happens
Trigger: pl.read_csv/scan_csv with with_schema_modify whose lambda returns None, a dict (name->name map), a generator, a tuple, or a list containing non-str elements.
Common situations: Users returning a dict for renaming (dict-style thinking), returning the input list unmodified but as a different type, accidentally returning a pandas Index or numpy array of names.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- python function failed
- activate object
- `arctan2` expected a `str` or `Expr` got a
- `arctan2` expected a `str` or `Expr` got a
- =" " should be a single byte character or empty, but is…
AI-assisted analysis of pola-rs/polars@fe841f959e (2026-09-18).
Data as JSON: /api/errors/fa58fa65d9106e0e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/polars-python/src/lazyframe/general.rs:291
.with_raise_if_empty(raise_if_empty)
.with_include_file_paths(include_file_paths.map(|x| x.into()))
.with_extra_columns_policy(extra_columns.0)
.with_missing_columns_policy(missing_columns.map(|x| x.0));
if let Some(new_columns) = new_columns {
r = r.with_column_names_overwrite(new_columns.0);
}
if let Some(lambda) = with_schema_modify {
let f = |schema: Schema| {
let iter = schema.iter_names().map(|s| s.as_str());
Python::attach(|py| {
let names = PyList::new(py, iter).unwrap();
let out = lambda.call1(py, (names,)).expect("python function failed");
let new_names = out
.extract::<Vec<String>>(py)
.expect("python function should return List[str]");
polars_ensure!(new_names.len() == schema.len(),
ShapeMismatch: "The length of the new names list should be equal to or less than the original column length",
);
Ok(schema
.iter_values()
.zip(new_names)
.map(|(dtype, name)| Field::new(name.into(), dtype.clone()))
.collect())
})
};
r = r.with_schema_modify(f).map_err(PyPolarsErr::from)?
}
Ok(r.finish().map_err(PyPolarsErr::from)?.into())
}
#[cfg(feature = "parquet")]
#[staticmethod]View on GitHub (pinned to fe841f959e)