From 6de5a598d68bc74996bc2fec811b443d28002a92 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 21 Jun 2025 19:12:25 +0100 Subject: [PATCH 1/4] refactor: Enforce `LazyExpr.from_elementwise` See https://github.com/narwhals-dev/narwhals/pull/2716#discussion_r2160078141 --- narwhals/_compliant/expr.py | 7 +++++- narwhals/_dask/expr.py | 1 + narwhals/_duckdb/expr.py | 34 +++++++++++++++++++++++++-- narwhals/_duckdb/namespace.py | 38 ++++++------------------------ narwhals/_ibis/expr.py | 23 +++++++++++++++++- narwhals/_ibis/namespace.py | 29 ++++++----------------- narwhals/_spark_like/expr.py | 35 +++++++++++++++++++++++++-- narwhals/_spark_like/namespace.py | 39 ++++++------------------------- 8 files changed, 115 insertions(+), 91 deletions(-) diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index 021dc8406d..baa94de8cb 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -32,7 +32,7 @@ from narwhals.dependencies import get_numpy, is_numpy_array if TYPE_CHECKING: - from collections.abc import Mapping, Sequence + from collections.abc import Iterable, Mapping, Sequence from typing_extensions import Self, TypeIs @@ -894,6 +894,11 @@ def fn(names: Sequence[str]) -> Sequence[str]: @classmethod def _alias_native(cls, expr: NativeExprT, name: str, /) -> NativeExprT: ... + @classmethod + def from_elementwise( + cls, func: Callable[[Iterable[NativeExprT]], NativeExprT], *exprs: Self + ) -> Self: ... + @property def name(self) -> LazyExprNameNamespace[Self]: return LazyExprNameNamespace(self) diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index 74cb30d5e1..d34ef2df31 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -683,3 +683,4 @@ def dt(self) -> DaskExprDateTimeNamespace: rank = not_implemented() # pyright: ignore[reportAssignmentType] _alias_native = not_implemented() window_function = not_implemented() # pyright: ignore[reportAssignmentType] + from_elementwise = not_implemented() diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index 6f407b5495..b586dee6ef 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -21,11 +21,15 @@ narwhals_to_native_dtype, when, ) -from narwhals._expression_parsing import ExprKind +from narwhals._expression_parsing import ( + ExprKind, + combine_alias_output_names, + combine_evaluate_output_names, +) from narwhals._utils import Implementation, not_implemented, requires if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Iterable, Sequence from duckdb import Expression from typing_extensions import Self @@ -213,6 +217,32 @@ def func(df: DuckDBLazyFrame) -> list[Expression]: version=context._version, ) + @classmethod + def from_elementwise( + cls, func: Callable[[Iterable[Expression]], Expression], *exprs: Self + ) -> Self: + def call(df: DuckDBLazyFrame) -> list[Expression]: + cols = (col for _expr in exprs for col in _expr(df)) + return [func(cols)] + + def window_function( + df: DuckDBLazyFrame, window_inputs: DuckDBWindowInputs + ) -> list[Expression]: + cols = ( + col for _expr in exprs for col in _expr.window_function(df, window_inputs) + ) + return [func(cols)] + + context = exprs[0] + return cls( + call=call, + window_function=window_function, + evaluate_output_names=combine_evaluate_output_names(*exprs), + alias_output_names=combine_alias_output_names(*exprs), + backend_version=context._backend_version, + version=context._version, + ) + def _callable_to_eval_series( self, call: Callable[..., Expression], /, **expressifiable_args: Self | Any ) -> EvalSeries[DuckDBLazyFrame, Expression]: diff --git a/narwhals/_duckdb/namespace.py b/narwhals/_duckdb/namespace.py index 97a28be986..03148a9fe4 100644 --- a/narwhals/_duckdb/namespace.py +++ b/narwhals/_duckdb/namespace.py @@ -3,7 +3,7 @@ import operator from functools import reduce from itertools import chain -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import duckdb from duckdb import CoalesceOperator, Expression, FunctionExpression @@ -49,30 +49,6 @@ def _expr(self) -> type[DuckDBExpr]: def _lazyframe(self) -> type[DuckDBLazyFrame]: return DuckDBLazyFrame - def _expr_from_elementwise( - self, func: Callable[[Iterable[Expression]], Expression], *exprs: DuckDBExpr - ) -> DuckDBExpr: - def call(df: DuckDBLazyFrame) -> list[Expression]: - cols = (col for _expr in exprs for col in _expr(df)) - return [func(cols)] - - def window_function( - df: DuckDBLazyFrame, window_inputs: DuckDBWindowInputs - ) -> list[Expression]: - cols = ( - col for _expr in exprs for col in _expr.window_function(df, window_inputs) - ) - return [func(cols)] - - return self._expr( - call=call, - window_function=window_function, - evaluate_output_names=combine_evaluate_output_names(*exprs), - alias_output_names=combine_alias_output_names(*exprs), - backend_version=self._backend_version, - version=self._version, - ) - def concat( self, items: Iterable[DuckDBLazyFrame], *, how: ConcatMethod ) -> DuckDBLazyFrame: @@ -119,31 +95,31 @@ def all_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: return reduce(operator.and_, cols) - return self._expr_from_elementwise(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def any_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: return reduce(operator.or_, cols) - return self._expr_from_elementwise(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def max_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: return FunctionExpression("greatest", *cols) - return self._expr_from_elementwise(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def min_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: return FunctionExpression("least", *cols) - return self._expr_from_elementwise(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def sum_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: return reduce(operator.add, (CoalesceOperator(col, lit(0)) for col in cols)) - return self._expr_from_elementwise(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def mean_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: @@ -152,7 +128,7 @@ def func(cols: Iterable[Expression]) -> Expression: operator.add, (CoalesceOperator(col, lit(0)) for col in cols) ) / reduce(operator.add, (col.isnotnull().cast(BIGINT) for col in cols)) - return self._expr_from_elementwise(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def when(self, predicate: DuckDBExpr) -> DuckDBWhen: return DuckDBWhen.from_expr(predicate, context=self) diff --git a/narwhals/_ibis/expr.py b/narwhals/_ibis/expr.py index 3b47d523de..f9ff1ad09c 100644 --- a/narwhals/_ibis/expr.py +++ b/narwhals/_ibis/expr.py @@ -8,6 +8,10 @@ from narwhals._compliant import LazyExpr from narwhals._compliant.window import WindowInputs +from narwhals._expression_parsing import ( + combine_alias_output_names, + combine_evaluate_output_names, +) from narwhals._ibis.expr_dt import IbisExprDateTimeNamespace from narwhals._ibis.expr_list import IbisExprListNamespace from narwhals._ibis.expr_str import IbisExprStringNamespace @@ -16,7 +20,7 @@ from narwhals._utils import Implementation, not_implemented if TYPE_CHECKING: - from collections.abc import Iterator, Sequence + from collections.abc import Iterable, Iterator, Sequence import ibis.expr.types as ir from typing_extensions import Self @@ -203,6 +207,23 @@ def func(df: IbisLazyFrame) -> list[ir.Column]: version=context._version, ) + @classmethod + def from_elementwise( + cls, func: Callable[[Iterable[ir.Value]], ir.Value], *exprs: Self + ) -> Self: + def call(df: IbisLazyFrame) -> list[ir.Value]: + cols = (col for _expr in exprs for col in _expr(df)) + return [func(cols)] + + context = exprs[0] + return cls( + call=call, + evaluate_output_names=combine_evaluate_output_names(*exprs), + alias_output_names=combine_alias_output_names(*exprs), + backend_version=context._backend_version, + version=context._version, + ) + def _with_callable( self, call: Callable[..., ir.Value], /, **expressifiable_args: Self | Any ) -> Self: diff --git a/narwhals/_ibis/namespace.py b/narwhals/_ibis/namespace.py index 4530b05c2e..24ef96686a 100644 --- a/narwhals/_ibis/namespace.py +++ b/narwhals/_ibis/namespace.py @@ -3,7 +3,7 @@ import operator from functools import reduce from itertools import chain -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, cast import ibis import ibis.expr.types as ir @@ -33,21 +33,6 @@ def __init__(self, *, backend_version: tuple[int, ...], version: Version) -> Non self._backend_version = backend_version self._version = version - def _expr_from_callable( - self, func: Callable[[Iterable[ir.Value]], ir.Value], *exprs: IbisExpr - ) -> IbisExpr: - def call(df: IbisLazyFrame) -> list[ir.Value]: - cols = (col for _expr in exprs for col in _expr(df)) - return [func(cols)] - - return self._expr( - call=call, - evaluate_output_names=combine_evaluate_output_names(*exprs), - alias_output_names=combine_alias_output_names(*exprs), - backend_version=self._backend_version, - version=self._version, - ) - @property def selectors(self) -> IbisSelectorNamespace: return IbisSelectorNamespace.from_namespace(self) @@ -104,32 +89,32 @@ def all_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: return reduce(operator.and_, cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def any_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: return reduce(operator.or_, cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def max_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: return ibis.greatest(*cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def min_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: return ibis.least(*cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def sum_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: cols = (col.fill_null(lit(0)) for col in cols) return reduce(operator.add, cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def mean_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: @@ -138,7 +123,7 @@ def func(cols: Iterable[ir.Value]) -> ir.Value: operator.add, (col.isnull().ifelse(lit(0), lit(1)) for col in cols) ) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) @requires.backend_version((10, 0)) def when(self, predicate: IbisExpr) -> IbisWhen: diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index 07c5124690..6970a695d5 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -5,7 +5,11 @@ from narwhals._compliant import LazyExpr from narwhals._compliant.window import WindowInputs -from narwhals._expression_parsing import ExprKind +from narwhals._expression_parsing import ( + ExprKind, + combine_alias_output_names, + combine_evaluate_output_names, +) from narwhals._spark_like.expr_dt import SparkLikeExprDateTimeNamespace from narwhals._spark_like.expr_list import SparkLikeExprListNamespace from narwhals._spark_like.expr_str import SparkLikeExprStringNamespace @@ -20,7 +24,7 @@ from narwhals.dependencies import get_pyspark if TYPE_CHECKING: - from collections.abc import Iterator, Mapping, Sequence + from collections.abc import Iterable, Iterator, Mapping, Sequence from sqlframe.base.column import Column from sqlframe.base.window import Window, WindowSpec @@ -277,6 +281,33 @@ def func(df: SparkLikeLazyFrame) -> list[Column]: implementation=context._implementation, ) + @classmethod + def from_elementwise( + cls, func: Callable[[Iterable[Column]], Column], *exprs: Self + ) -> Self: + def call(df: SparkLikeLazyFrame) -> list[Column]: + cols = (col for _expr in exprs for col in _expr(df)) + return [func(cols)] + + def window_function( + df: SparkLikeLazyFrame, window_inputs: SparkWindowInputs + ) -> list[Column]: + cols = ( + col for _expr in exprs for col in _expr.window_function(df, window_inputs) + ) + return [func(cols)] + + context = exprs[0] + return cls( + call=call, + window_function=window_function, + evaluate_output_names=combine_evaluate_output_names(*exprs), + alias_output_names=combine_alias_output_names(*exprs), + backend_version=context._backend_version, + version=context._version, + implementation=context._implementation, + ) + def _callable_to_eval_series( self, call: Callable[..., Column], /, **expressifiable_args: Self | Any ) -> EvalSeries[SparkLikeLazyFrame, Column]: diff --git a/narwhals/_spark_like/namespace.py b/narwhals/_spark_like/namespace.py index f2b6ec843c..bacd35626d 100644 --- a/narwhals/_spark_like/namespace.py +++ b/narwhals/_spark_like/namespace.py @@ -2,7 +2,7 @@ import operator from functools import reduce -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING from narwhals._compliant import LazyNamespace, LazyThen, LazyWhen from narwhals._expression_parsing import ( @@ -73,31 +73,6 @@ def _native_dtypes(self): # type: ignore[no-untyped-def] # noqa: ANN202 else: return import_native_dtypes(self._implementation) - def _expr_from_callable( - self, func: Callable[[Iterable[Column]], Column], *exprs: SparkLikeExpr - ) -> SparkLikeExpr: - def call(df: SparkLikeLazyFrame) -> list[Column]: - cols = (col for _expr in exprs for col in _expr(df)) - return [func(cols)] - - def window_function( - df: SparkLikeLazyFrame, window_inputs: SparkWindowInputs - ) -> list[Column]: - cols = ( - col for _expr in exprs for col in _expr.window_function(df, window_inputs) - ) - return [func(cols)] - - return self._expr( - call=call, - window_function=window_function, - evaluate_output_names=combine_evaluate_output_names(*exprs), - alias_output_names=combine_alias_output_names(*exprs), - backend_version=self._backend_version, - version=self._version, - implementation=self._implementation, - ) - def lit(self, value: NonNestedLiteral, dtype: IntoDType | None) -> SparkLikeExpr: def _lit(df: SparkLikeLazyFrame) -> list[Column]: column = df._F.lit(value) @@ -135,25 +110,25 @@ def all_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: return reduce(operator.and_, cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def any_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: return reduce(operator.or_, cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def max_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: return self._F.greatest(*cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def min_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: return self._F.least(*cols) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def sum_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: @@ -161,7 +136,7 @@ def func(cols: Iterable[Column]) -> Column: operator.add, (self._F.coalesce(col, self._F.lit(0)) for col in cols) ) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def mean_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: @@ -182,7 +157,7 @@ def func(cols: Iterable[Column]) -> Column: ), ) - return self._expr_from_callable(func, *exprs) + return self._expr.from_elementwise(func, *exprs) def concat( self, items: Iterable[SparkLikeLazyFrame], *, how: ConcatMethod From ceb4e8d81f519b89914acb93c0ab6c72028a8648 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Fri, 27 Jun 2025 10:25:43 +0100 Subject: [PATCH 2/4] rename to _from_elementwise_horizontal_op --- narwhals/_compliant/expr.py | 2 +- narwhals/_duckdb/expr.py | 2 +- narwhals/_duckdb/namespace.py | 12 ++++++------ narwhals/_ibis/expr.py | 2 +- narwhals/_ibis/namespace.py | 12 ++++++------ narwhals/_spark_like/expr.py | 2 +- narwhals/_spark_like/namespace.py | 12 ++++++------ 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index 6bf0b40181..945ffafeea 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -895,7 +895,7 @@ def fn(names: Sequence[str]) -> Sequence[str]: def _alias_native(cls, expr: NativeExprT, name: str, /) -> NativeExprT: ... @classmethod - def from_elementwise( + def _from_elementwise_horizontal_op( cls, func: Callable[[Iterable[NativeExprT]], NativeExprT], *exprs: Self ) -> Self: ... diff --git a/narwhals/_duckdb/expr.py b/narwhals/_duckdb/expr.py index 6db629848c..a59f3ee589 100644 --- a/narwhals/_duckdb/expr.py +++ b/narwhals/_duckdb/expr.py @@ -222,7 +222,7 @@ def func(df: DuckDBLazyFrame) -> list[Expression]: ) @classmethod - def from_elementwise( + def _from_elementwise_horizontal_op( cls, func: Callable[[Iterable[Expression]], Expression], *exprs: Self ) -> Self: def call(df: DuckDBLazyFrame) -> list[Expression]: diff --git a/narwhals/_duckdb/namespace.py b/narwhals/_duckdb/namespace.py index 533a706d91..bfc84632e7 100644 --- a/narwhals/_duckdb/namespace.py +++ b/narwhals/_duckdb/namespace.py @@ -100,7 +100,7 @@ def func(cols: Iterable[Expression]) -> Expression: ) return reduce(operator.and_, it) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def any_horizontal(self, *exprs: DuckDBExpr, ignore_nulls: bool) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: @@ -111,25 +111,25 @@ def func(cols: Iterable[Expression]) -> Expression: ) return reduce(operator.or_, it) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def max_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: return F("greatest", *cols) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def min_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: return F("least", *cols) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def sum_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: return reduce(operator.add, (CoalesceOperator(col, lit(0)) for col in cols)) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def mean_horizontal(self, *exprs: DuckDBExpr) -> DuckDBExpr: def func(cols: Iterable[Expression]) -> Expression: @@ -138,7 +138,7 @@ def func(cols: Iterable[Expression]) -> Expression: operator.add, (CoalesceOperator(col, lit(0)) for col in cols) ) / reduce(operator.add, (col.isnotnull().cast(BIGINT) for col in cols)) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def when(self, predicate: DuckDBExpr) -> DuckDBWhen: return DuckDBWhen.from_expr(predicate, context=self) diff --git a/narwhals/_ibis/expr.py b/narwhals/_ibis/expr.py index f9ff1ad09c..77a9afb24e 100644 --- a/narwhals/_ibis/expr.py +++ b/narwhals/_ibis/expr.py @@ -208,7 +208,7 @@ def func(df: IbisLazyFrame) -> list[ir.Column]: ) @classmethod - def from_elementwise( + def _from_elementwise_horizontal_op( cls, func: Callable[[Iterable[ir.Value]], ir.Value], *exprs: Self ) -> Self: def call(df: IbisLazyFrame) -> list[ir.Value]: diff --git a/narwhals/_ibis/namespace.py b/narwhals/_ibis/namespace.py index 22adc29f37..5c8dd40e54 100644 --- a/narwhals/_ibis/namespace.py +++ b/narwhals/_ibis/namespace.py @@ -94,7 +94,7 @@ def func(cols: Iterable[ir.Value]) -> ir.Value: ) return reduce(operator.and_, it) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def any_horizontal(self, *exprs: IbisExpr, ignore_nulls: bool) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: @@ -105,26 +105,26 @@ def func(cols: Iterable[ir.Value]) -> ir.Value: ) return reduce(operator.or_, it) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def max_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: return ibis.greatest(*cols) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def min_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: return ibis.least(*cols) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def sum_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: cols = (col.fill_null(lit(0)) for col in cols) return reduce(operator.add, cols) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def mean_horizontal(self, *exprs: IbisExpr) -> IbisExpr: def func(cols: Iterable[ir.Value]) -> ir.Value: @@ -133,7 +133,7 @@ def func(cols: Iterable[ir.Value]) -> ir.Value: operator.add, (col.isnull().ifelse(lit(0), lit(1)) for col in cols) ) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) @requires.backend_version((10, 0)) def when(self, predicate: IbisExpr) -> IbisWhen: diff --git a/narwhals/_spark_like/expr.py b/narwhals/_spark_like/expr.py index 6970a695d5..39d44d4e8f 100644 --- a/narwhals/_spark_like/expr.py +++ b/narwhals/_spark_like/expr.py @@ -282,7 +282,7 @@ def func(df: SparkLikeLazyFrame) -> list[Column]: ) @classmethod - def from_elementwise( + def _from_elementwise_horizontal_op( cls, func: Callable[[Iterable[Column]], Column], *exprs: Self ) -> Self: def call(df: SparkLikeLazyFrame) -> list[Column]: diff --git a/narwhals/_spark_like/namespace.py b/narwhals/_spark_like/namespace.py index fd1aa63ec1..08482835fc 100644 --- a/narwhals/_spark_like/namespace.py +++ b/narwhals/_spark_like/namespace.py @@ -115,7 +115,7 @@ def func(cols: Iterable[Column]) -> Column: ) return reduce(operator.and_, it) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def any_horizontal(self, *exprs: SparkLikeExpr, ignore_nulls: bool) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: @@ -126,19 +126,19 @@ def func(cols: Iterable[Column]) -> Column: ) return reduce(operator.or_, it) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def max_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: return self._F.greatest(*cols) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def min_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: return self._F.least(*cols) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def sum_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: @@ -146,7 +146,7 @@ def func(cols: Iterable[Column]) -> Column: operator.add, (self._F.coalesce(col, self._F.lit(0)) for col in cols) ) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def mean_horizontal(self, *exprs: SparkLikeExpr) -> SparkLikeExpr: def func(cols: Iterable[Column]) -> Column: @@ -167,7 +167,7 @@ def func(cols: Iterable[Column]) -> Column: ), ) - return self._expr.from_elementwise(func, *exprs) + return self._expr._from_elementwise_horizontal_op(func, *exprs) def concat( self, items: Iterable[SparkLikeLazyFrame], *, how: ConcatMethod From ac88ddcc5b87fa8563bc44b28d6920a54747ef1e Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Fri, 27 Jun 2025 10:30:31 +0100 Subject: [PATCH 3/4] fix typing --- narwhals/_dask/expr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index d34ef2df31..39158a299b 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -683,4 +683,4 @@ def dt(self) -> DaskExprDateTimeNamespace: rank = not_implemented() # pyright: ignore[reportAssignmentType] _alias_native = not_implemented() window_function = not_implemented() # pyright: ignore[reportAssignmentType] - from_elementwise = not_implemented() + _from_horizontal_op = not_implemented() From 91e814d4579b4b9a5232412b68758a76b2f727ba Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Fri, 27 Jun 2025 10:32:08 +0100 Subject: [PATCH 4/4] actually fix typing --- narwhals/_dask/expr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py index 39158a299b..b79b3c64a9 100644 --- a/narwhals/_dask/expr.py +++ b/narwhals/_dask/expr.py @@ -683,4 +683,4 @@ def dt(self) -> DaskExprDateTimeNamespace: rank = not_implemented() # pyright: ignore[reportAssignmentType] _alias_native = not_implemented() window_function = not_implemented() # pyright: ignore[reportAssignmentType] - _from_horizontal_op = not_implemented() + _from_elementwise_horizontal_op = not_implemented()