pola-rs/polars · error · TypeError

`new` argument is required if `old` argument is not a Mappin

Error message

`new` argument is required if `old` argument is not a Mapping type

What it means

Expr.replace(old, new) raises TypeError when `new` is omitted and `old` is not a Mapping. The two supported call shapes are replace(mapping) (dict of old->new) or replace(old, new) with both positional arguments; a bare scalar/list old with no new is meaningless. Note the check uses the NO_DEFAULT sentinel, so new=None does not satisfy it either.

Source

Thrown at py-polars/src/polars/expr/expr.py:12268

                " Use `replace_strict` instead to set a return data type while replacing values.",
                version="1.0.0",
            )
        if default is not NO_DEFAULT:
            issue_deprecation_warning(
                "the `default` parameter for `replace` is deprecated."
                " Use `replace_strict` instead to set a default while replacing values.",
                version="1.0.0",
            )
            return self.replace_strict(
                old, new, default=default, return_dtype=return_dtype
            )

        if new is NO_DEFAULT:
            if not isinstance(old, Mapping):
                msg = (
                    "`new` argument is required if `old` argument is not a Mapping type"
                )
                raise TypeError(msg)
            new = list(old.values())
            old = list(old.keys())
        else:
            if isinstance(old, Sequence) and not isinstance(old, (str, pl.Series)):
                old = pl.Series(old)
            if isinstance(new, Sequence) and not isinstance(new, (str, pl.Series)):
                new = pl.Series(new)

        old_pyexpr = parse_into_expression(old, str_as_lit=True)  # type: ignore[arg-type]
        new_pyexpr = parse_into_expression(new, str_as_lit=True)

        result = wrap_expr(self._pyexpr.replace(old_pyexpr, new_pyexpr))

        if return_dtype is not None:
            result = result.cast(return_dtype)

        return result

View on GitHub (pinned to df599052da)

Solutions

  1. Pass both arguments: pl.col('a').replace(2, 10).
  2. Or pass a dict: pl.col('a').replace({2: 10, 3: 30}).
  3. In wrapper functions, use a sentinel (NO_DEFAULT-style object), not None, to detect 'not provided'.

Example fix

# before
pl.col('a').replace(2)

# after
pl.col('a').replace(2, 10)
# or
pl.col('a').replace({2: 10})
Defensive patterns

Strategy: type-guard

Validate before calling

from collections.abc import Mapping
if new is None and not isinstance(old, Mapping):
    raise TypeError('replace needs `new` unless `old` is a mapping')

Type guard

from collections.abc import Mapping

def has_full_replace_args(old, new) -> bool:
    return isinstance(old, Mapping) or new is not None

Prevention

When it happens

Trigger: pl.col('a').replace(2), pl.col('a').replace([1, 2]) without new, or replace(old=2, new=None) via optional parameters that default to None.

Common situations: Wrapping replace in a helper where new is optional (default None instead of NO_DEFAULT); assuming old alone is a no-op filter; upgrading code where a default value used to be positional.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/9f82f5a17fd618a8. Report an issue: GitHub.