Skip to content

Commit 805dbde

Browse files
authored
API: avoid passing Manager to subclass __init__ (#57553)
1 parent 069f9a4 commit 805dbde

File tree

4 files changed

+55
-28
lines changed

4 files changed

+55
-28
lines changed

pandas/core/frame.py

+24-13
Original file line numberDiff line numberDiff line change
@@ -653,25 +653,36 @@ def _constructor(self) -> type[DataFrame]:
653653
return DataFrame
654654

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

663-
_constructor_sliced: Callable[..., Series] = Series
668+
# We assume that the subclass __init__ knows how to handle a
669+
# pd.DataFrame object.
670+
return self._constructor(df)
664671

665-
def _sliced_from_mgr(self, mgr, axes) -> Series:
666-
return Series._from_mgr(mgr, axes)
672+
_constructor_sliced: Callable[..., Series] = Series
667673

668674
def _constructor_sliced_from_mgr(self, mgr, axes) -> Series:
669-
if self._constructor_sliced is Series:
670-
ser = self._sliced_from_mgr(mgr, axes)
671-
ser._name = None # caller is responsible for setting real name
675+
ser = Series._from_mgr(mgr, axes)
676+
ser._name = None # caller is responsible for setting real name
677+
678+
if type(self) is DataFrame:
679+
# This would also work `if self._constructor_sliced is Series`, but
680+
# this check is slightly faster, benefiting the most-common case.
672681
return ser
673-
assert axes is mgr.axes
674-
return self._constructor_sliced(mgr)
682+
683+
# We assume that the subclass __init__ knows how to handle a
684+
# pd.Series object.
685+
return self._constructor_sliced(ser)
675686

676687
# ----------------------------------------------------------------------
677688
# Constructors

pandas/core/generic.py

+1
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ def _init_mgr(
287287
mgr = mgr.astype(dtype=dtype)
288288
return mgr
289289

290+
@final
290291
@classmethod
291292
def _from_mgr(cls, mgr: Manager, axes: list[Index]) -> Self:
292293
"""

pandas/core/series.py

+19-15
Original file line numberDiff line numberDiff line change
@@ -576,14 +576,17 @@ def _constructor(self) -> type[Series]:
576576
return Series
577577

578578
def _constructor_from_mgr(self, mgr, axes):
579-
if self._constructor is Series:
580-
# we are pandas.Series (or a subclass that doesn't override _constructor)
581-
ser = Series._from_mgr(mgr, axes=axes)
582-
ser._name = None # caller is responsible for setting real name
579+
ser = Series._from_mgr(mgr, axes=axes)
580+
ser._name = None # caller is responsible for setting real name
581+
582+
if type(self) is Series:
583+
# This would also work `if self._constructor is Series`, but
584+
# this check is slightly faster, benefiting the most-common case.
583585
return ser
584-
else:
585-
assert axes is mgr.axes
586-
return self._constructor(mgr)
586+
587+
# We assume that the subclass __init__ knows how to handle a
588+
# pd.Series object.
589+
return self._constructor(ser)
587590

588591
@property
589592
def _constructor_expanddim(self) -> Callable[..., DataFrame]:
@@ -595,18 +598,19 @@ def _constructor_expanddim(self) -> Callable[..., DataFrame]:
595598

596599
return DataFrame
597600

598-
def _expanddim_from_mgr(self, mgr, axes) -> DataFrame:
601+
def _constructor_expanddim_from_mgr(self, mgr, axes):
599602
from pandas.core.frame import DataFrame
600603

601-
return DataFrame._from_mgr(mgr, axes=mgr.axes)
604+
df = DataFrame._from_mgr(mgr, axes=mgr.axes)
602605

603-
def _constructor_expanddim_from_mgr(self, mgr, axes):
604-
from pandas.core.frame import DataFrame
606+
if type(self) is Series:
607+
# This would also work `if self._constructor_expanddim is DataFrame`,
608+
# but this check is slightly faster, benefiting the most-common case.
609+
return df
605610

606-
if self._constructor_expanddim is DataFrame:
607-
return self._expanddim_from_mgr(mgr, axes)
608-
assert axes is mgr.axes
609-
return self._constructor_expanddim(mgr)
611+
# We assume that the subclass __init__ knows how to handle a
612+
# pd.DataFrame object.
613+
return self._constructor_expanddim(df)
610614

611615
# types
612616
@property

pandas/tests/frame/test_subclass.py

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616

1717

1818
class TestDataFrameSubclassing:
19+
def test_no_warning_on_mgr(self):
20+
# GH#57032
21+
df = tm.SubclassedDataFrame(
22+
{"X": [1, 2, 3], "Y": [1, 2, 3]}, index=["a", "b", "c"]
23+
)
24+
with tm.assert_produces_warning(None):
25+
# df.isna() goes through _constructor_from_mgr, which we want to
26+
# *not* pass a Manager do __init__
27+
df.isna()
28+
df["X"].isna()
29+
1930
def test_frame_subclassing_and_slicing(self):
2031
# Subclass frame and ensure it returns the right class on slicing it
2132
# In reference to PR 9632

0 commit comments

Comments
 (0)