Skip to content

Commit 810b2d0

Browse files
authored
Backport PR #57553 on branch 2.2.x (API: avoid passing Manager to subclass init) (#58008)
* Backport PR #57553: API: avoid passing Manager to subclass __init__ * whatsnew, type ignores * merge 2.2.2 file from main * rebase on 2.2.x whatsnew
1 parent f455401 commit 810b2d0

File tree

5 files changed

+62
-32
lines changed

5 files changed

+62
-32
lines changed

pandas/core/frame.py

+29-16
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
@@ -1403,7 +1414,8 @@ def _get_values_for_csv(
14031414
na_rep=na_rep,
14041415
quoting=quoting,
14051416
)
1406-
return self._constructor_from_mgr(mgr, axes=mgr.axes)
1417+
# error: Incompatible return value type (got "DataFrame", expected "Self")
1418+
return self._constructor_from_mgr(mgr, axes=mgr.axes) # type: ignore[return-value]
14071419

14081420
# ----------------------------------------------------------------------
14091421

@@ -5077,7 +5089,8 @@ def predicate(arr: ArrayLike) -> bool:
50775089
return True
50785090

50795091
mgr = self._mgr._get_data_subset(predicate).copy(deep=None)
5080-
return self._constructor_from_mgr(mgr, axes=mgr.axes).__finalize__(self)
5092+
# error: Incompatible return value type (got "DataFrame", expected "Self")
5093+
return self._constructor_from_mgr(mgr, axes=mgr.axes).__finalize__(self) # type: ignore[return-value]
50815094

50825095
def insert(
50835096
self,

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/resample.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2548,7 +2548,8 @@ def _take_new_index(
25482548
if axis == 1:
25492549
raise NotImplementedError("axis 1 is not supported")
25502550
new_mgr = obj._mgr.reindex_indexer(new_axis=new_index, indexer=indexer, axis=1)
2551-
return obj._constructor_from_mgr(new_mgr, axes=new_mgr.axes)
2551+
# error: Incompatible return value type (got "DataFrame", expected "NDFrameT")
2552+
return obj._constructor_from_mgr(new_mgr, axes=new_mgr.axes) # type: ignore[return-value]
25522553
else:
25532554
raise ValueError("'obj' should be either a Series or a DataFrame")
25542555

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)