Repository: pandas-dev/pandas
Pull request #55892 (2023-11-09 07:56:03)

Backport PR #55764 on branch 2.1.x (REGR: fix return class in _constructor_from_mgr for simple subclasses)
Backport PR #55764: REGR: fix return class in _constructor_from_mgr for simple subclasses

lithomas1: I think we might need the fix in #55885 for this.
jorisvandenbossche: I think it is https://github.com/pandas-dev/pandas/pull/55427 that needs to be backported
jorisvandenbossche: Removed my temp commits again

Patch:
diff --git a/doc/source/whatsnew/v2.1.3.rst b/doc/source/whatsnew/v2.1.3.rst
index 3b1cd1c152..264cd44090 100644
--- a/doc/source/whatsnew/v2.1.3.rst
+++ b/doc/source/whatsnew/v2.1.3.rst
@@ -13,7 +13,7 @@ including other versions of pandas.
 
 Fixed regressions
 ~~~~~~~~~~~~~~~~~
--
+- Fixed infinite recursion from operations that return a new object on some DataFrame subclasses (:issue:`55763`)
 -
 
 .. ---------------------------------------------------------------------------
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
index c109070ce4..605cf49856 100644
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -641,7 +641,7 @@ class DataFrame(NDFrame, OpsMixin):
     def _constructor_from_mgr(self, mgr, axes):
         if self._constructor is DataFrame:
             # we are pandas.DataFrame (or a subclass that doesn't override _constructor)
-            return self._from_mgr(mgr, axes=axes)
+            return DataFrame._from_mgr(mgr, axes=axes)
         else:
             assert axes is mgr.axes
             return self._constructor(mgr)
diff --git a/pandas/core/series.py b/pandas/core/series.py
index 8ad049e173..7b22d89bfe 100644
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -581,7 +581,7 @@ class Series(base.IndexOpsMixin, NDFrame):  # type: ignore[misc]
     def _constructor_from_mgr(self, mgr, axes):
         if self._constructor is Series:
             # we are pandas.Series (or a subclass that doesn't override _constructor)
-            ser = self._from_mgr(mgr, axes=axes)
+            ser = Series._from_mgr(mgr, axes=axes)
             ser._name = None  # caller is responsible for setting real name
             return ser
         else:


Test patch:
diff --git a/pandas/tests/frame/test_subclass.py b/pandas/tests/frame/test_subclass.py
index 8e657f197c..ef78ae62cb 100644
--- a/pandas/tests/frame/test_subclass.py
+++ b/pandas/tests/frame/test_subclass.py
@@ -771,3 +771,44 @@ def test_constructor_with_metadata():
     )
     subset = df[["A", "B"]]
     assert isinstance(subset, MySubclassWithMetadata)
+
+
+class SimpleDataFrameSubClass(DataFrame):
+    """A subclass of DataFrame that does not define a constructor."""
+
+
+class SimpleSeriesSubClass(Series):
+    """A subclass of Series that does not define a constructor."""
+
+
+class TestSubclassWithoutConstructor:
+    def test_copy_df(self):
+        expected = DataFrame({"a": [1, 2, 3]})
+        result = SimpleDataFrameSubClass(expected).copy()
+
+        assert (
+            type(result) is DataFrame
+        )  # assert_frame_equal only checks isinstance(lhs, type(rhs))
+        tm.assert_frame_equal(result, expected)
+
+    def test_copy_series(self):
+        expected = Series([1, 2, 3])
+        result = SimpleSeriesSubClass(expected).copy()
+
+        tm.assert_series_equal(result, expected)
+
+    def test_series_to_frame(self):
+        orig = Series([1, 2, 3])
+        expected = orig.to_frame()
+        result = SimpleSeriesSubClass(orig).to_frame()
+
+        assert (
+            type(result) is DataFrame
+        )  # assert_frame_equal only checks isinstance(lhs, type(rhs))
+        tm.assert_frame_equal(result, expected)
+
+    def test_groupby(self):
+        df = SimpleDataFrameSubClass(DataFrame({"a": [1, 2, 3]}))
+
+        for _, v in df.groupby("a"):
+            assert type(v) is DataFrame