Skip to content

Commit 913b56f

Browse files
committed
Backport PR pandas-dev#57553: API: avoid passing Manager to subclass __init__
1 parent 7e8d492 commit 913b56f

File tree

4 files changed

+56
-29
lines changed

4 files changed

+56
-29
lines changed

pandas/core/frame.py

+25-14
Original file line numberDiff line numberDiff line change
@@ -656,26 +656,37 @@ class DataFrame(NDFrame, OpsMixin):
656656
def _constructor(self) -> Callable[..., DataFrame]:
657657
return DataFrame
658658

659-
def _constructor_from_mgr(self, mgr, axes):
660-
if self._constructor is DataFrame:
661-
# we are pandas.DataFrame (or a subclass that doesn't override _constructor)
662-
return DataFrame._from_mgr(mgr, axes=axes)
663-
else:
664-
assert axes is mgr.axes
659+
def _constructor_from_mgr(self, mgr, axes) -> DataFrame:
660+
df = DataFrame._from_mgr(mgr, axes=axes)
661+
662+
if type(self) is DataFrame:
663+
# This would also work `if self._constructor is DataFrame`, but
664+
# this check is slightly faster, benefiting the most-common case.
665+
return df
666+
667+
elif type(self).__name__ == "GeoDataFrame":
668+
# Shim until geopandas can override their _constructor_from_mgr
669+
# bc they have different behavior for Managers than for DataFrames
665670
return self._constructor(mgr)
666671

672+
# We assume that the subclass __init__ knows how to handle a
673+
# pd.DataFrame object.
674+
return self._constructor(df)
675+
667676
_constructor_sliced: Callable[..., Series] = Series
668677

669-
def _sliced_from_mgr(self, mgr, axes) -> Series:
670-
return Series._from_mgr(mgr, axes)
678+
def _constructor_sliced_from_mgr(self, mgr, axes) -> Series:
679+
ser = Series._from_mgr(mgr, axes)
680+
ser._name = None # caller is responsible for setting real name
671681

672-
def _constructor_sliced_from_mgr(self, mgr, axes):
673-
if self._constructor_sliced is Series:
674-
ser = self._sliced_from_mgr(mgr, axes)
675-
ser._name = None # caller is responsible for setting real name
682+
if type(self) is DataFrame:
683+
# This would also work `if self._constructor_sliced is Series`, but
684+
# this check is slightly faster, benefiting the most-common case.
676685
return ser
677-
assert axes is mgr.axes
678-
return self._constructor_sliced(mgr)
686+
687+
# We assume that the subclass __init__ knows how to handle a
688+
# pd.Series object.
689+
return self._constructor_sliced(ser)
679690

680691
# ----------------------------------------------------------------------
681692
# Constructors

pandas/core/generic.py

+1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ def _as_manager(self, typ: str, copy: bool_t = True) -> Self:
336336
# fastpath of passing a manager doesn't check the option/manager class
337337
return self._constructor_from_mgr(new_mgr, axes=new_mgr.axes).__finalize__(self)
338338

339+
@final
339340
@classmethod
340341
def _from_mgr(cls, mgr: Manager, axes: list[Index]) -> Self:
341342
"""

pandas/core/series.py

+19-15
Original file line numberDiff line numberDiff line change
@@ -662,14 +662,17 @@ def _constructor(self) -> Callable[..., Series]:
662662
return Series
663663

664664
def _constructor_from_mgr(self, mgr, axes):
665-
if self._constructor is Series:
666-
# we are pandas.Series (or a subclass that doesn't override _constructor)
667-
ser = Series._from_mgr(mgr, axes=axes)
668-
ser._name = None # caller is responsible for setting real name
665+
ser = Series._from_mgr(mgr, axes=axes)
666+
ser._name = None # caller is responsible for setting real name
667+
668+
if type(self) is Series:
669+
# This would also work `if self._constructor is Series`, but
670+
# this check is slightly faster, benefiting the most-common case.
669671
return ser
670-
else:
671-
assert axes is mgr.axes
672-
return self._constructor(mgr)
672+
673+
# We assume that the subclass __init__ knows how to handle a
674+
# pd.Series object.
675+
return self._constructor(ser)
673676

674677
@property
675678
def _constructor_expanddim(self) -> Callable[..., DataFrame]:
@@ -681,18 +684,19 @@ def _constructor_expanddim(self) -> Callable[..., DataFrame]:
681684

682685
return DataFrame
683686

684-
def _expanddim_from_mgr(self, mgr, axes) -> DataFrame:
687+
def _constructor_expanddim_from_mgr(self, mgr, axes):
685688
from pandas.core.frame import DataFrame
686689

687-
return DataFrame._from_mgr(mgr, axes=mgr.axes)
690+
df = DataFrame._from_mgr(mgr, axes=mgr.axes)
688691

689-
def _constructor_expanddim_from_mgr(self, mgr, axes):
690-
from pandas.core.frame import DataFrame
692+
if type(self) is Series:
693+
# This would also work `if self._constructor_expanddim is DataFrame`,
694+
# but this check is slightly faster, benefiting the most-common case.
695+
return df
691696

692-
if self._constructor_expanddim is DataFrame:
693-
return self._expanddim_from_mgr(mgr, axes)
694-
assert axes is mgr.axes
695-
return self._constructor_expanddim(mgr)
697+
# We assume that the subclass __init__ knows how to handle a
698+
# pd.DataFrame object.
699+
return self._constructor_expanddim(df)
696700

697701
# types
698702
@property

pandas/tests/frame/test_subclass.py

+11
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ def _constructor(self):
2626

2727

2828
class TestDataFrameSubclassing:
29+
def test_no_warning_on_mgr(self):
30+
# GH#57032
31+
df = tm.SubclassedDataFrame(
32+
{"X": [1, 2, 3], "Y": [1, 2, 3]}, index=["a", "b", "c"]
33+
)
34+
with tm.assert_produces_warning(None):
35+
# df.isna() goes through _constructor_from_mgr, which we want to
36+
# *not* pass a Manager do __init__
37+
df.isna()
38+
df["X"].isna()
39+
2940
def test_frame_subclassing_and_slicing(self):
3041
# Subclass frame and ensure it returns the right class on slicing it
3142
# In reference to PR 9632

0 commit comments

Comments
 (0)