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

  1. Return a plain Python list of str: lambda names: [n.lower() for n in names].
  2. If you have a dict mapping, convert: lambda names: [mapping.get(n, n) for n in names].
  3. Convert numpy/pandas name collections: lambda names: [str(n) for n in list(names_obj)].
  4. 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

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


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)