pola-rs/polars · error · NotImplementedError

require dict mapping

Error message

require dict mapping

What it means

When translating a UDF that calls dict.replace_strict (or a mapped replace_strict via a variable looked up in the caller's frame), the parser requires that the referenced variable be a plain dict so it can inline it as a mapping. If the caller-local variable resolved by name is not a dict, NotImplementedError('require dict mapping') is raised.

Solutions

  1. Make the mapping a plain built-in dict literal or dict variable visible to the UDF's caller scope.
  2. Use replace (non-strict) or explicit when/then chains instead of replace_strict with a non-dict mapping.
  3. Perform the replacement outside the UDF using a native expression: pl.col('a').replace_strict(mapping).

Example fix

// before
def fn(v): return str(v).replace_strict(labels)
// after
labels = {'a': 1, 'b': 2}  # plain dict in caller scope
def fn(v): return v.replace_strict(labels)
Defensive patterns

Strategy: type-guard

Validate before calling

# before using replace_strict inside a UDF, ensure the mapping is a plain dict
assert isinstance(labels, dict), 'replace_strict mapping must be a plain dict'

Type guard

def is_plain_dict(v) -> bool:
    return type(v) is dict

Try / catch

try:
    result = df.select(pl.col('a').map_elements(fn))
except NotImplementedError as e:
    if 'require dict mapping' in str(e):
        result = df.select(pl.col('a').replace_strict(dict(labels)))

Prevention

When it happens

Trigger: Inside a map_elements UDF, calling something.replace_strict(x) where x is a local variable in the enclosing function that is not a dict (e.g. a Series, defaultdict-like wrapper, or non-dict mapping); detected via _get_all_caller_variables in _expr.

Common situations: Passing a collections.OrderedDict subclass, a polars Series, or a custom Mapping to replace_strict from within a UDF; defining the mapping in an outer scope the parser cannot resolve as a dict.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of pola-rs/polars@fe841f959e (2026-09-18). Data as JSON: /api/errors/28feab7da5ea7925. Report an issue: GitHub.

Appendix: source

Thrown at py-polars/src/polars/_utils/udfs.py:725

                return f"{op}{e1}"
            else:
                e2 = self._expr(value.right_operand, col, param_name, depth + 1)
                if op in ("is", "is not") and value.left_operand == "None":
                    not_ = "" if op == "is" else "not_"
                    return f"{e1}.is_{not_}null()"
                elif op in ("in", "not in"):
                    not_ = "" if op == "in" else "~"
                    return (
                        f"{not_}({e1}.is_in({e2}))"
                        if " " in e1
                        else f"{not_}{e1}.is_in({e2})"
                    )
                elif op == "replace_strict":
                    if not self._caller_variables:
                        self._caller_variables = _get_all_caller_variables()
                    if not isinstance(self._caller_variables.get(e1, None), dict):
                        msg = "require dict mapping"
                        raise NotImplementedError(msg)
                    return f"{e2}.{op}({e1})"
                elif op == "<<":
                    # 2**e2 may be float if e2 was -ve, but if e1 << e2 was valid then
                    # e2 must have been +ve. therefore 2**e2 can be safely cast to
                    # i64, which may be necessary if chaining ops that assume i64.
                    return f"({e1} * 2**{e2}).cast(pl.Int64)"
                elif op == ">>":
                    # (motivation for the cast is same as the '<<' case above)
                    return f"({e1} / 2**{e2}).cast(pl.Int64)"
                else:
                    expr = f"{e1} {op} {e2}"
                    return f"({expr})" if depth else expr

        elif value == param_name:
            return f'pl.col("{col}")'

        return value

View on GitHub (pinned to fe841f959e)