Repository: narwhals-dev/narwhals

Issue:
[Bug]: Series.dtype might be incorrect after inplace modification of pandas series
### Describe the bug

```
In [1]: import narwhals as nw

In [2]: import pandas as pd

In [3]: s = pd.Series([1,2,3])

In [4]: snw = nw.from_native(s, series_only=True)

In [5]: snw.dtype
Out[5]: Int64

In [6]: s[0] = 999.5
<ipython-input-6-f533a70d1055>:1: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise an error in a future version of pandas. Value '999.5' has dtype incompatible with int64, please explicitly cast to a compatible dtype first.
  s[0] = 999.5

In [7]: snw.dtype
Out[7]: Int64

In [8]: snw.to_native()
Out[8]: 
0    999.5
1      2.0
2      3.0
dtype: float64
```

### Steps or code to reproduce the bug

above

### Expected results

`snw.dtype` should always be correct

### Actual results

it's incorrect

### Please run narwhals.show_version() and enter the output below.

```shell
latest commit

I think we need to revert https://github.com/narwhals-dev/narwhals/pull/1255
```


### Relevant log output

_No response_

Discussion:

Patch:
diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py
index 50b309d4..bb60de12 100644
--- a/narwhals/_pandas_like/dataframe.py
+++ b/narwhals/_pandas_like/dataframe.py
@@ -54,8 +54,6 @@ class PandasLikeDataFrame:
         self._backend_version = backend_version
         self._dtypes = dtypes
 
-        self._schema_cache: dict[str, DType] | None = None
-
     def __narwhals_dataframe__(self) -> Self:
         return self
 
@@ -305,14 +303,12 @@ class PandasLikeDataFrame:
 
     @property
     def schema(self) -> dict[str, DType]:
-        if self._schema_cache is None:
-            self._schema_cache = {
-                col: native_to_narwhals_dtype(
-                    self._native_frame[col], self._dtypes, self._implementation
-                )
-                for col in self._native_frame.columns
-            }
-        return self._schema_cache
+        return {
+            col: native_to_narwhals_dtype(
+                self._native_frame[col], self._dtypes, self._implementation
+            )
+            for col in self._native_frame.columns
+        }
 
     def collect_schema(self) -> dict[str, DType]:
         return self.schema
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index 3ea4d225..35df78e2 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -89,8 +89,6 @@ class PandasLikeSeries:
         self._backend_version = backend_version
         self._dtypes = dtypes
 
-        self._dtype_cache: DType | None = None
-
         # In pandas, copy-on-write becomes the default in version 3.
         # So, before that, we need to explicitly avoid unnecessary
         # copies by using `copy=False` sometimes.
@@ -172,11 +170,9 @@ class PandasLikeSeries:
 
     @property
     def dtype(self: Self) -> DType:
-        if self._dtype_cache is None:
-            self._dtype_cache = native_to_narwhals_dtype(
-                self._native_series, self._dtypes, self._implementation
-            )
-        return self._dtype_cache
+        return native_to_narwhals_dtype(
+            self._native_series, self._dtypes, self._implementation
+        )
 
     def scatter(self, indices: int | Sequence[int], values: Any) -> Self:
         if isinstance(values, self.__class__):


Test patch:
diff --git a/tests/dtypes_test.py b/tests/dtypes_test.py
index a8c4ee81..0d6363ae 100644
--- a/tests/dtypes_test.py
+++ b/tests/dtypes_test.py
@@ -161,3 +161,18 @@ def test_second_time_unit() -> None:
     s = pa.chunked_array([pa.array([timedelta(1)], type=pa.duration("s"))])
     result = nw.from_native(s, series_only=True)
     assert result.dtype == nw.Duration("s")
+
+
+@pytest.mark.filterwarnings("ignore:Setting an item of incompatible")
+def test_pandas_inplace_modification_1267(request: pytest.FixtureRequest) -> None:
+    if PANDAS_VERSION >= (3,):
+        # pandas 3.0+ won't allow this kind of inplace modification
+        request.applymarker(pytest.mark.xfail)
+    if PANDAS_VERSION < (1, 4):
+        # pandas pre 1.4 wouldn't change the type?
+        request.applymarker(pytest.mark.xfail)
+    s = pd.Series([1, 2, 3])
+    snw = nw.from_native(s, series_only=True)
+    assert snw.dtype == nw.Int64
+    s[0] = 999.5
+    assert snw.dtype == nw.Float64