BUG: Passing original properties of `DataFrame` and `Series` subclasses to their constructors
State: closed

- [x] closes #34177 BUG: Subclassed DataFrame doesn't persist \_metadata properties across binary operations
- [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.
No new arguments, methods nor 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.

While implementing a subclass of `Dataframe`, I found that in some operations, objects that subclass's `DataFrame` and `Series` forget their original properties.

```python
import pandas as pd

class SubclassedSeries(pd.Series):
    _metadata = ['original_property']
    def __init__(self, data=None, original_property=None, *args, **kwargs):
        super().__init__(data, *args, **kwargs)
        self.original_property = original_property
    @property
    def _constructor(self):
        return SubclassedSeries
    @property
    def _constructor_expanddim(self):
        return SubclassedDataFrame


class SubclassedDataFrame(pd.DataFrame):
    _metadata = ['original_property']
    def __init__(self, data=None, original_property=None, *args, **kwargs):
        super().__init__(data, *args, **kwargs)
        self.original_property = original_property
    @property
    def _constructor(self):
        return SubclassedDataFrame
    @property
    def _constructor_sliced(self):
        return SubclassedSeries

## __init__
df = SubclassedDataFrame({'lkey': ['foo', 'bar', 'baz', 'foo'], 'value': [1, 2, 3, 5]}, original_property='original_property')
print(f'__init__: {df.original_property}')

## Select
select_df = df[df['value'] == '1']
print(f'Select: {df.original_property}')

## loc
loc_df = df.loc[df['lkey'] == 'foo']
print(f'loc: {loc_df.original_property}')

## Concat
df = pd.concat([df, df])
print(f'Concat: {df.original_property}')

## Merge
df1 = SubclassedDataFrame({'lkey': ['foo', 'bar', 'baz', 'foo'],
                   'value': [1, 2, 3, 5]}, original_property='original_property')
df2 = SubclassedDataFrame({'rkey': ['foo', 'bar', 'baz', 'foo'],
                   'value': [5, 6, 7, 8]}, original_property='original_property')
merged_df = df1.merge(df2, left_on='lkey', right_on='rkey')
print(f'Merge: {merged_df.original_property}')

## Series
series = df['value']
print(f'Series: {series.original_property}')

## Sum
sum_df = df1 + df2
print(f'Sum: {sum_df.original_property}')

(df * 2).added_property
```
This functionality is critical for my project so I decided to fix it. I had the following considerations:

- `__init__` parameters in both parent classes weren't modified.
- `_name` stays in `Series._metadata` and their children because [@540db96b](https://github.com/pandas-dev/pandas/commit/540db96b8179c2ed83e427b9d501b04d68ee1804). It is removed right before the calling the constructor and added again in `__init__` of parent class.
- I added a unit test covering the cases mentioned above. I mentioned #34177 but I think #32860, #35415, #32638, #19850 and #8572 are related.
- Each `DataFrame` child must come with their respective `Series` child (and vice versa). Both children must contain and handle the same `_metadata` parameters.
- The constructors' parameters of both children must begin with `data=None`. The example with `(self, data=None, **_metadata, *args, **kwargs)` was the cleanest I was able to think without having to clean `*args` and `**kwargs` (like the old unit test in pandas/tests/frame/test_subclass.py:MySubclassWithMetadata). Not enforcing `data` as the first parameter and having to clean up `*args` and `**kwargs` felt as wrong as adding `*args` and `**kwargs` to `__init__` in both parents.

My environment consist on SUSE 15.6 and Python 3.11.11

Edit: Removed WIP comments about tests and code checks.

Labels:
name: Stale

Comments:
Any chance to review this PR anytime soon?
@noatamir @TomAugspurger  @jorisvandenbossche 
This pull request is stale because it has been open for thirty days with no activity. Please [update](https://pandas.pydata.org/pandas-docs/stable/development/contributing.html#updating-your-pull-request) and respond to this comment if you're still interested in working on this.
Thanks for the pull request, but it appears to have gone stale. If interested in continuing, please merge in the main branch, address any review comments and/or failing tests, and we can reopen.