Repository: narwhals-dev/narwhals (python)

Issue:
[Enh]: `get_native_namespace` could check several objects
When figuring out how to do support for narwhals in fairlearn, I was concerned with how to make sure that a user passes objects from the same dataframe library, which would then determine the namespace of returned objects.

I have seen there is `nw.get_native_namespace()` and I would suggest an enhancement for it similar to something we have in scikit-learn: There is a [get_namespace()](https://github.com/scikit-learn/scikit-learn/blob/5f50a8706f46e971aa053791a1441ac8a4ebd623/sklearn/utils/_array_api.py#L497) function for Array API support and you can pass multiple objects to check. 

`get_native_namespace` could also error or return False if the objects don't share a common namespace. 

I would find it practical for the current use case in fairlearn and I would imagine other projects would also benefit from it. Would it make sense to add a functionality like this one?

Pull request description:
feat: support passing multiple arguments positionally to get_native_namespace


<!--
# Thanks for contributing a pull request! 
## Please make sure you see our contribution guidelines: https://github.com/narwhals-dev/narwhals/blob/main/CONTRIBUTING.md
-->

## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues

- Related issue #\<issue number\>
- Closes #2160

## Checklist

- [ ] Code follows style guide (ruff)
- [ ] Tests added
- [ ] Documented the changes

## If you have comments or can explain your changes, please do so below


Patch:
diff --git a/narwhals/translate.py b/narwhals/translate.py
index e545c272..bac75b67 100644
--- a/narwhals/translate.py
+++ b/narwhals/translate.py
@@ -40,11 +40,6 @@ from narwhals.dependencies import is_sqlframe_dataframe
 from narwhals.utils import Version
 
 if TYPE_CHECKING:
-    import pandas as pd
-    import polars as pl
-    import pyarrow as pa
-
-    from narwhals._arrow.typing import ArrowChunkedArray
     from narwhals.dataframe import DataFrame
     from narwhals.dataframe import LazyFrame
     from narwhals.series import Series
@@ -790,21 +785,14 @@ def _from_native_impl(  # noqa: PLR0915
 
 
 def get_native_namespace(
-    obj: DataFrame[Any]
-    | LazyFrame[Any]
-    | Series[Any]
-    | pd.DataFrame
-    | pd.Series[Any]
-    | pl.DataFrame
-    | pl.LazyFrame
-    | pl.Series
-    | pa.Table
-    | ArrowChunkedArray,
+    *obj: DataFrame[Any] | LazyFrame[Any] | Series[Any] | IntoFrame | IntoSeries,
 ) -> Any:
     """Get native namespace from object.
 
     Arguments:
-        obj: Dataframe, Lazyframe, or Series.
+        obj: Dataframe, Lazyframe, or Series. Multiple objects can be
+            passed positionally, in which case they must all have the
+            same native namespace (else an error is raised).
 
     Returns:
         Native module.
@@ -820,6 +808,19 @@ def get_native_namespace(
         >>> nw.get_native_namespace(df)
         <module 'polars'...>
     """
+    if not obj:
+        msg = "At least one object must be passed to `get_native_namespace`."
+        raise ValueError(msg)
+    result = {_get_native_namespace_single_obj(x) for x in obj}
+    if len(result) != 1:
+        msg = f"Found objects with different native namespaces: {result}."
+        raise ValueError(msg)
+    return result.pop()
+
+
+def _get_native_namespace_single_obj(
+    obj: DataFrame[Any] | LazyFrame[Any] | Series[Any] | IntoFrame | IntoSeries,
+) -> Any:
     from narwhals.utils import has_native_namespace
 
     if has_native_namespace(obj):


Test patch:
diff --git a/tests/translate/get_native_namespace_test.py b/tests/translate/get_native_namespace_test.py
index be10e825..786cb645 100644
--- a/tests/translate/get_native_namespace_test.py
+++ b/tests/translate/get_native_namespace_test.py
@@ -19,16 +19,22 @@ def test_native_namespace() -> None:
     assert nw.get_native_namespace(df.to_native()) is pl
     assert nw.get_native_namespace(df.lazy().to_native()) is pl
     assert nw.get_native_namespace(df["a"].to_native()) is pl
+    assert nw.get_native_namespace(df, df["a"].to_native()) is pl
     df = nw.from_native(pd.DataFrame({"a": [1, 2, 3]}), eager_only=True)
     assert nw.get_native_namespace(df) is pd
     assert nw.get_native_namespace(df.to_native()) is pd
     assert nw.get_native_namespace(df["a"].to_native()) is pd
+    assert nw.get_native_namespace(df, df["a"].to_native()) is pd
     df = nw.from_native(pa.table({"a": [1, 2, 3]}), eager_only=True)
     assert nw.get_native_namespace(df) is pa
     assert nw.get_native_namespace(df.to_native()) is pa
-    assert nw.get_native_namespace(df["a"].to_native()) is pa
+    assert nw.get_native_namespace(df, df["a"].to_native()) is pa
 
 
 def test_get_native_namespace_invalid() -> None:
     with pytest.raises(TypeError, match="Could not get native namespace"):
         nw.get_native_namespace(1)  # type: ignore[arg-type]
+    with pytest.raises(ValueError, match="At least one object"):
+        nw.get_native_namespace()
+    with pytest.raises(ValueError, match="Found objects with different"):
+        nw.get_native_namespace(pd.Series([1]), pl.Series([2]))