REGR: fix return class in _constructor_from_mgr for simple subclasses
State: closed

- [x] closes #55763 (Replace xxxx with the GitHub issue number)
- [x] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [x] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.

This is a draft for fixing #55763. I'm not familiar with this part of the code, but this seems to fix the issue locally.

This is addressing changes brought in by https://github.com/pandas-dev/pandas/pull/54922. Instead of checking the class of `._constructor` in `_constructor_from_mgr`, this checks the class being called.

We were relying on most operations on our subclass returning a pandas dataframe. AFAICT, the previous PR changed the behavior so that most operations would return the class of the instance even if `_constructor` was not set.


Once I see that this doesn't break tests on CI, added tests will be like:

```python
class SimpleSubClass(DataFrame):
    """A subclass of DataFrame that does not define a constructor."""
    pass

class TestSubclassWithoutConstructor():

    def test_copy(self):
        expected = DataFrame({"a": [1, 2, 3]})
        result = SimpleSubClass(expected).copy()

        tm.assert_frame_equal(result, expected)

    def test_groupby(self):
        df = SimpleSubClass(DataFrame({"a": [1, 2, 3]}))

        for _, v in df.groupby("a"):
            assert isinstance(v, DataFrame)
```

added to `test_subclass.py`.

Labels:
name: Regression
description: Functionality that used to work in a prior pandas version
name: Subclassing
description: Subclassing pandas objects

Comments:
cc @jorisvandenbossche im fine with the change here bc it improves perf the base classes.  no strong opinion on the actual behavior.
>  it improves perf the base classes

Do you mean that the check `type(self) is DataFrame` is a bit faster than `self._constructor is DataFrame`? (it's indeed a few nanoseconds faster)
I've addressed the comment, added a test for subclasses of Series, and added a release note.

This does throw a depreciation warning:

```python
import pandas as pd

class SimpleDataFrameSubClass(pd.DataFrame):
    """A subclass of DataFrame that does not define a constructor."""

s = SimpleDataFrameSubClass(pd.DataFrame({"a": [1, 2, 3]}))
s.copy()
```

```
<ipython-input-2-6e1d2ee6d3ad>:1: DeprecationWarning: Passing a BlockManager to DataFrame is deprecated and will raise in a future version. Use public APIs instead.
  s.copy()
```

But [the previous fix seems to have done this as well](https://github.com/jorisvandenbossche/pandas/blob/0ffd772716969816388cff9597e5f5c941350458/pandas/tests/groupby/test_groupby_subclass.py#L14-L16).

It would make sense if this didn't warn, since the user code in my example above can't obviously do anything about this warning besides silence it. It may be out of scope for this regression though, since this PR does not introduce this warning.
Yes, we still have to figure out how to deal with this deprecation warning in subclasses. But, that deprecation warning is only for pandas 2.2, and so won't yet be visible in a pandas 2.1.3. 

And I think my other comment might solve this warning for the simple case where the subclass doesn't override the constructor.
Thanks @ivirshup !