pola-rs/polars · error
python function should return List[str]
Error message
python function should return List[str]
What it means
After the with_schema_modify callback runs, its return value is extracted as Vec<String>. If the callable returns anything else (list of bytes, a single string, a dict, None), extraction fails and this expect panics with 'python function should return List[str]'. A separate, proper polars error is raised only later if the length mismatches the schema.
Source
Thrown at crates/polars-python/src/lazyframe/general.rs:292
.with_glob(glob)
.with_raise_if_empty(raise_if_empty)
.with_include_file_paths(include_file_paths.map(|x| x.into()))
.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 df599052da)
Solutions
- Return a plain list of str with exactly one entry per input name
- Wrap generators/comprehensions with list(...) and ensure each element is a str
- Annotate the callback as Callable[[list[str]], list[str]] and test it standalone
- Check the length matches the input; mismatch triggers a separate ShapeMismatch error
Example fix
# before rename = lambda names: [n.encode() for n in names] # list[bytes] -> panic # after rename = lambda names: [str(n).lower() for n in names] # list[str]
Defensive patterns
Strategy: type-guard
Validate before calling
out = rename(names) assert type(out) is list, "must return list, not generator/tuple/str" assert all(isinstance(n, str) for n in out), "elements must be str, not bytes"
Type guard
def returns_str_list(f, names: list[str]) -> bool:
out = f(list(names))
return isinstance(out, list) and len(out) == len(names) and all(type(n) is str for n in out) Prevention
- Wrap generator expressions with list(...)
- Type-annotate callbacks Callable[[list[str]], list[str]]
- Return str(n) for any element that may not be str
When it happens
Trigger: Callbacks returning List[bytes] (e.g. [n.encode() for n in names]), a generator, a tuple, a numpy array of objects, or a plain string instead of a list of str, passed as with_schema_modify to a lazy scan.
Common situations: Encoding renames for a different API, returning map(names) results from a function that yields non-strings, pandas-flavored callbacks returning an Index.
Related errors
- python function failed
- negative stop is not supported for lazy slices
- cannot describe a LazyFrame that has no columns
- invalid type for `on_columns` argument: {qualified_type_name
- There is no natural representation of DayTime in JSON.
AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16).
Data as JSON: /api/errors/fa58fa65d9106e0e.
Report an issue: GitHub.