diff --git a/pandas/core/arrays/_mixins.py b/pandas/core/arrays/_mixins.py index 674379f6d65f8..9d534a5a8d815 100644 --- a/pandas/core/arrays/_mixins.py +++ b/pandas/core/arrays/_mixins.py @@ -16,6 +16,8 @@ from pandas._libs import lib from pandas._libs.arrays import NDArrayBacked from pandas._typing import ( + ArrayLike, + Dtype, F, PositionalIndexer2D, PositionalIndexerTuple, @@ -34,8 +36,15 @@ validate_insert_loc, ) -from pandas.core.dtypes.common import is_dtype_equal -from pandas.core.dtypes.dtypes import ExtensionDtype +from pandas.core.dtypes.common import ( + is_dtype_equal, + pandas_dtype, +) +from pandas.core.dtypes.dtypes import ( + DatetimeTZDtype, + ExtensionDtype, + PeriodDtype, +) from pandas.core.dtypes.missing import array_equivalent from pandas.core import missing @@ -101,6 +110,41 @@ def _validate_scalar(self, value): # ------------------------------------------------------------------------ + def view(self, dtype: Dtype | None = None) -> ArrayLike: + # We handle datetime64, datetime64tz, timedelta64, and period + # dtypes here. Everything else we pass through to the underlying + # ndarray. + if dtype is None or dtype is self.dtype: + return self._from_backing_data(self._ndarray) + + if isinstance(dtype, type): + # we sometimes pass non-dtype objects, e.g np.ndarray; + # pass those through to the underlying ndarray + return self._ndarray.view(dtype) + + dtype = pandas_dtype(dtype) + arr = self._ndarray + + if isinstance(dtype, (PeriodDtype, DatetimeTZDtype)): + cls = dtype.construct_array_type() + return cls(arr.view("i8"), dtype=dtype) + elif dtype == "M8[ns]": + from pandas.core.arrays import DatetimeArray + + return DatetimeArray(arr.view("i8"), dtype=dtype) + elif dtype == "m8[ns]": + from pandas.core.arrays import TimedeltaArray + + return TimedeltaArray(arr.view("i8"), dtype=dtype) + + # error: Incompatible return value type (got "ndarray", expected + # "ExtensionArray") + # error: Argument "dtype" to "view" of "_ArrayOrScalarCommon" has incompatible + # type "Union[ExtensionDtype, dtype[Any]]"; expected "Union[dtype[Any], None, + # type, _SupportsDType, str, Union[Tuple[Any, int], Tuple[Any, Union[int, + # Sequence[int]]], List[Any], _DTypeDict, Tuple[Any, Any]]]" + return arr.view(dtype=dtype) # type: ignore[return-value,arg-type] + def take( self: NDArrayBackedExtensionArrayT, indices: TakeIndexer, diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 79d5d104b5764..33da9ca858a4c 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -85,11 +85,7 @@ is_unsigned_integer_dtype, pandas_dtype, ) -from pandas.core.dtypes.dtypes import ( - DatetimeTZDtype, - ExtensionDtype, - PeriodDtype, -) +from pandas.core.dtypes.dtypes import ExtensionDtype from pandas.core.dtypes.missing import ( is_valid_na_for_dtype, isna, @@ -461,36 +457,9 @@ def view(self, dtype: Dtype | None = ...) -> ArrayLike: ... def view(self, dtype: Dtype | None = None) -> ArrayLike: - # We handle datetime64, datetime64tz, timedelta64, and period - # dtypes here. Everything else we pass through to the underlying - # ndarray. - if dtype is None or dtype is self.dtype: - return type(self)(self._ndarray, dtype=self.dtype) - - if isinstance(dtype, type): - # we sometimes pass non-dtype objects, e.g np.ndarray; - # pass those through to the underlying ndarray - return self._ndarray.view(dtype) - - dtype = pandas_dtype(dtype) - if isinstance(dtype, (PeriodDtype, DatetimeTZDtype)): - cls = dtype.construct_array_type() - return cls(self.asi8, dtype=dtype) - elif dtype == "M8[ns]": - from pandas.core.arrays import DatetimeArray - - return DatetimeArray(self.asi8, dtype=dtype) - elif dtype == "m8[ns]": - from pandas.core.arrays import TimedeltaArray - - return TimedeltaArray(self.asi8, dtype=dtype) - # error: Incompatible return value type (got "ndarray", expected - # "ExtensionArray") - # error: Argument "dtype" to "view" of "_ArrayOrScalarCommon" has incompatible - # type "Union[ExtensionDtype, dtype[Any]]"; expected "Union[dtype[Any], None, - # type, _SupportsDType, str, Union[Tuple[Any, int], Tuple[Any, Union[int, - # Sequence[int]]], List[Any], _DTypeDict, Tuple[Any, Any]]]" - return self._ndarray.view(dtype=dtype) # type: ignore[return-value,arg-type] + # we need to explicitly call super() method as long as the `@overload`s + # are present in this file. + return super().view(dtype) # ------------------------------------------------------------------ # ExtensionArray Interface diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 220b43f323a5f..bc591d49add5e 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -947,7 +947,6 @@ def view(self, cls=None): # e.g. m8[s] return self._data.view(cls) - arr = self._data.view("i8") idx_cls = self._dtype_to_subclass(dtype) arr_cls = idx_cls._data_cls arr = arr_cls(self._data.view("i8"), dtype=dtype) diff --git a/pandas/core/series.py b/pandas/core/series.py index 93a68ef3703fd..03fac7cceabb7 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -802,9 +802,11 @@ def view(self, dtype: Dtype | None = None) -> Series: 4 2 dtype: int8 """ - return self._constructor( - self._values.view(dtype), index=self.index - ).__finalize__(self, method="view") + # self.array instead of self._values so we piggyback on PandasArray + # implementation + res_values = self.array.view(dtype) + res_ser = self._constructor(res_values, index=self.index) + return res_ser.__finalize__(self, method="view") # ---------------------------------------------------------------------- # NDArray Compat diff --git a/pandas/tests/series/methods/test_view.py b/pandas/tests/series/methods/test_view.py index 818023c01e4e7..22902c8648fc5 100644 --- a/pandas/tests/series/methods/test_view.py +++ b/pandas/tests/series/methods/test_view.py @@ -11,6 +11,18 @@ class TestView: + def test_view_i8_to_datetimelike(self): + dti = date_range("2000", periods=4, tz="US/Central") + ser = Series(dti.asi8) + + result = ser.view(dti.dtype) + tm.assert_datetime_array_equal(result._values, dti._data._with_freq(None)) + + pi = dti.tz_localize(None).to_period("D") + ser = Series(pi.asi8) + result = ser.view(pi.dtype) + tm.assert_period_array_equal(result._values, pi._data) + def test_view_tz(self): # GH#24024 ser = Series(date_range("2000", periods=4, tz="US/Central"))