From b4c31b383c05bc331fe481525e2c76fb1ac5e37b Mon Sep 17 00:00:00 2001 From: Francesco Bruzzesi <42817048+FBruzzesi@users.noreply.github.com> Date: Tue, 24 Sep 2024 09:26:36 +0200 Subject: [PATCH] feat: add Series.__iter__ (#1057) --- docs/api-reference/series.md | 1 + narwhals/_arrow/series.py | 4 ++++ narwhals/_expression_parsing.py | 8 ++------ narwhals/_pandas_like/series.py | 4 ++++ narwhals/series.py | 4 ++++ tests/series_only/__iter___test.py | 16 ++++++++++++++++ utils/check_api_reference.py | 1 + 7 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 tests/series_only/__iter___test.py diff --git a/docs/api-reference/series.md b/docs/api-reference/series.md index 9868e7b98..5adaa57ab 100644 --- a/docs/api-reference/series.md +++ b/docs/api-reference/series.md @@ -6,6 +6,7 @@ members: - __arrow_c_stream__ - __getitem__ + - __iter__ - abs - alias - all diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py index 09fade682..8c8643791 100644 --- a/narwhals/_arrow/series.py +++ b/narwhals/_arrow/series.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import Iterable +from typing import Iterator from typing import Literal from typing import Sequence from typing import overload @@ -702,6 +703,9 @@ def mode(self: Self) -> ArrowSeries: plx.col(col_token) == plx.col(col_token).max() )[self.name] + def __iter__(self: Self) -> Iterator[Any]: + yield from self._native_series.__iter__() + @property def shape(self) -> tuple[int]: return (len(self._native_series),) diff --git a/narwhals/_expression_parsing.py b/narwhals/_expression_parsing.py index 10d686ab1..3ccf906ff 100644 --- a/narwhals/_expression_parsing.py +++ b/narwhals/_expression_parsing.py @@ -12,7 +12,6 @@ from typing import overload from narwhals.dependencies import is_numpy_array -from narwhals.utils import flatten if TYPE_CHECKING: from narwhals._arrow.dataframe import ArrowDataFrame @@ -95,7 +94,7 @@ def evaluate_into_exprs( """Evaluate each expr into Series.""" series: ListOfCompliantSeries = [ # type: ignore[assignment] item - for sublist in (evaluate_into_expr(df, into_expr) for into_expr in flatten(exprs)) + for sublist in (evaluate_into_expr(df, into_expr) for into_expr in exprs) for item in sublist ] for name, expr in named_exprs.items(): @@ -157,9 +156,7 @@ def parse_into_exprs( ) -> ListOfCompliantExpr: """Parse each input as an expression (if it's not already one). See `parse_into_expr` for more details.""" - return [ - parse_into_expr(into_expr, namespace=namespace) for into_expr in flatten(exprs) - ] + [ + return [parse_into_expr(into_expr, namespace=namespace) for into_expr in exprs] + [ parse_into_expr(expr, namespace=namespace).alias(name) for name, expr in named_exprs.items() ] @@ -181,7 +178,6 @@ def parse_into_expr( - if it's a string, then convert it to an expression - else, raise """ - if hasattr(into_expr, "__narwhals_expr__"): return into_expr # type: ignore[return-value] if hasattr(into_expr, "__narwhals_series__"): diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py index 1b40d69de..873683dd5 100644 --- a/narwhals/_pandas_like/series.py +++ b/narwhals/_pandas_like/series.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import Iterable +from typing import Iterator from typing import Literal from typing import Sequence from typing import overload @@ -665,6 +666,9 @@ def mode(self: Self) -> Self: result.name = native_series.name return self._from_native_series(result) + def __iter__(self: Self) -> Iterator[Any]: + yield from self._native_series.__iter__() + @property def str(self) -> PandasLikeSeriesStringNamespace: return PandasLikeSeriesStringNamespace(self) diff --git a/narwhals/series.py b/narwhals/series.py index 619e4b553..ae78eef76 100644 --- a/narwhals/series.py +++ b/narwhals/series.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import Callable +from typing import Iterator from typing import Literal from typing import Sequence from typing import overload @@ -2407,6 +2408,9 @@ def mode(self: Self) -> Self: """ return self._from_compliant_series(self._compliant_series.mode()) + def __iter__(self: Self) -> Iterator[Any]: + yield from self._compliant_series.__iter__() + @property def str(self) -> SeriesStringNamespace: return SeriesStringNamespace(self) diff --git a/tests/series_only/__iter___test.py b/tests/series_only/__iter___test.py new file mode 100644 index 000000000..d190cd80a --- /dev/null +++ b/tests/series_only/__iter___test.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from collections.abc import Iterable +from typing import Any + +import narwhals.stable.v1 as nw +from tests.utils import compare_dicts + +data = [1, 2, 3] + + +def test_to_list(constructor_eager: Any) -> None: + s = nw.from_native(constructor_eager({"a": data}), eager_only=True)["a"] + + assert isinstance(s, Iterable) + compare_dicts({"a": [x for x in s]}, {"a": [1, 2, 3]}) # noqa: C416 diff --git a/utils/check_api_reference.py b/utils/check_api_reference.py index a56b28c58..d11c2bc99 100644 --- a/utils/check_api_reference.py +++ b/utils/check_api_reference.py @@ -28,6 +28,7 @@ "item", "scatter", "to_native", + "__iter__", } BASE_DTYPES = {"NumericType", "DType", "TemporalType"}