Repository: narwhals-dev/narwhals
Pull request #1052 (2024-09-23 19:49:53)

[pre-commit.ci] pre-commit autoupdate
<!--pre-commit.ci start-->
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.5 → v0.6.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.5...v0.6.7)
<!--pre-commit.ci end-->


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e97fce29..08d274f8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,7 +1,7 @@
 repos:
 - repo: https://github.com/astral-sh/ruff-pre-commit
   # Ruff version.
-  rev: 'v0.6.5'
+  rev: 'v0.6.7'
   hooks:
     # Run the formatter.
     - id: ruff-format
diff --git a/docs/api-reference/series.md b/docs/api-reference/series.md
index 9868e7b9..5adaa57a 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 09fade68..8c864379 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -3,6 +3,7 @@ from __future__ import annotations
 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 @@ class 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 10d686ab..3ccf906f 100644
--- a/narwhals/_expression_parsing.py
+++ b/narwhals/_expression_parsing.py
@@ -12,7 +12,6 @@ from typing import cast
 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 1b40d69d..873683dd 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -3,6 +3,7 @@ from __future__ import annotations
 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 @@ class PandasLikeSeries:
         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/dataframe.py b/narwhals/dataframe.py
index 83ebdcdc..581c8617 100644
--- a/narwhals/dataframe.py
+++ b/narwhals/dataframe.py
@@ -41,9 +41,6 @@ class BaseFrame(Generic[FrameT]):
     _compliant_frame: Any
     _level: Literal["full", "interchange"]
 
-    def __len__(self) -> Any:
-        return self._compliant_frame.__len__()
-
     def __native_namespace__(self) -> Any:
         return self._compliant_frame.__native_namespace__()
 
@@ -316,6 +313,9 @@ class DataFrame(BaseFrame[FrameT]):
             msg = f"Expected an object which implements `__narwhals_dataframe__`, got: {type(df)}"
             raise AssertionError(msg)
 
+    def __len__(self) -> Any:
+        return self._compliant_frame.__len__()
+
     def __array__(self, dtype: Any = None, copy: bool | None = None) -> np.ndarray:
         return self._compliant_frame.__array__(dtype, copy=copy)
 
diff --git a/narwhals/series.py b/narwhals/series.py
index 619e4b55..ae78eef7 100644
--- a/narwhals/series.py
+++ b/narwhals/series.py
@@ -3,6 +3,7 @@ from __future__ import annotations
 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 @@ class Series:
         """
         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/utils/check_api_reference.py b/utils/check_api_reference.py
index a56b28c5..d11c2bc9 100644
--- a/utils/check_api_reference.py
+++ b/utils/check_api_reference.py
@@ -28,6 +28,7 @@ SERIES_ONLY_METHODS = {
     "item",
     "scatter",
     "to_native",
+    "__iter__",
 }
 BASE_DTYPES = {"NumericType", "DType", "TemporalType"}
 


Test patch:
diff --git a/tests/frame/len_test.py b/tests/frame/len_test.py
index 6cb9269d..c06884e0 100644
--- a/tests/frame/len_test.py
+++ b/tests/frame/len_test.py
@@ -9,5 +9,5 @@ data = {
 
 
 def test_len(constructor_eager: Any) -> None:
-    result = len(nw.from_native(constructor_eager(data)))
+    result = len(nw.from_native(constructor_eager(data), eager_only=True))
     assert result == 4
diff --git a/tests/series_only/__iter___test.py b/tests/series_only/__iter___test.py
new file mode 100644
index 00000000..d190cd80
--- /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