DOC: move info docs to DataFrameInfo
State: closed

- [ ] closes #xxxx
- [ ] tests added / passed
- [ ] passes `black pandas`
- [ ] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [ ] whatsnew entry

Precursor for #37320
Move ``info`` docstring from ``pandas/core/frame.py`` to class ``DataFrameInfo`` using ``doc`` decorator.

@simonjayhawkins need your help here as discussed previously.

Labels:
name: Docs

Comments:
@ivanovmg can you merge master
This pull request is stale because it has been open for thirty days with no activity. Please update or respond to this comment if you're still interested in working on this.
@ivanovmg if you can merge master
Merged.
@simonjayhawkins I need help on creating a shared docstring for ``DataFrame.info()`` and ``Series.info()``.
Currently this implementation fails as apparently I do not use ``doc`` properly.
Can you please help?
@datapythonista can you please review the workaround I have just commited?
My understanding here is that we have a docstring in an object, and we want to reuse in another object too. With some small changes. This is done by:

```python
@doc(name='render1')
def render1():
    """
    Docstring or {name}.
    """

@doc(render1, name='render2')
def render2():
    pass
```

With Appender and Supstitution, it made more sense to have docstrings in variables. here you write it normally in one object, and then pass that object name for `doc` for the other object to know where to get the base docstring for.

Then, if it makes things simpler, change what's being reused. In many cases it's preferrable to duplicate a bit of the docstring, or make a part of it not so generic, that have huge blocks of codes in the variables.

The important part is that once you finished the changes, you run `validate_docstrings.py` for your objects, check that things look good, that the variables are rendering well and with the values corresponding to the right object. And ideally, you can also build the html for the docs locally, and check that things look good.
My original intention was to do the following.

```
from abc import ABC, abstractmethod

from pandas.util._decorators import doc


class BaseInfo(ABC):
    @doc(klass='Base')
    @abstractmethod
    def render(self):
        """Info for {klass}"""


class DataFrameInfo(BaseInfo):
    @doc(BaseInfo.render, klass='DataFrame')
    def render(self):
        pass


class SeriesInfo(BaseInfo):
    @doc(BaseInfo.render, klass='Series')
    def render(self):
        pass


class DataFrame:
    @doc(DataFrameInfo.render)
    def info(self):
        return DataFrameInfo().render()


if __name__ == '__main__':
    print(f'BaseInfo.render(): {BaseInfo.render.__doc__}')
    print(f'DataFrameInfo.render(): {DataFrameInfo.render.__doc__}')
    print(f'SeriesInfo.render(): {SeriesInfo.render.__doc__}')
    print(f'DataFrame.info(): {DataFrame.info.__doc__}')
```

However, the problem is that when executing this script we get the following error:
```
Traceback (most recent call last):
  File "pandas\temp_doc.py", line 26, in <module>
    class DataFrame:
  File "pandas\temp_doc.py", line 28, in DataFrame
    def info(self):
  File "c:\users\mivanov4\code\pandas\pandas\util\_decorators.py", line 387, in decorator
    decorated.__doc__ = "".join(
  File "c:\users\mivanov4\code\pandas\pandas\util\_decorators.py", line 388, in <genexpr>
    component.format(**params)
KeyError: 'klass'
```

I guess the problem is that ``doc`` decorator does not support such multiple redirections.

Therefore, I extracted a constant ``INFO_DOCSTRING`` and formatted it.

======================
In comparison:

```
from abc import ABC, abstractmethod

from pandas.util._decorators import doc

INFO_DOCSTRING = """Info for {klass} in the constant"""

class BaseInfo(ABC):
    @doc(klass='Base')
    @abstractmethod
    def render(self):
        """Info for {klass} in the base class"""


class DataFrameInfo(BaseInfo):
    @doc(BaseInfo.render, klass='DataFrame')
    def render(self):
        pass


class SeriesInfo(BaseInfo):
    @doc(BaseInfo.render, klass='Series')
    def render(self):
        pass


class DataFrame:
    @doc(INFO_DOCSTRING, klass='DataFrame')
    def info(self):
        return DataFrameInfo().render()


if __name__ == '__main__':
    print(f'BaseInfo.render(): {BaseInfo.render.__doc__}')
    print(f'DataFrameInfo.render(): {DataFrameInfo.render.__doc__}')
    print(f'SeriesInfo.render(): {SeriesInfo.render.__doc__}')
    print(f'DataFrame.info(): {DataFrame.info.__doc__}')
```

Gives output:
```
BaseInfo.render(): Info for Base in the base class
DataFrameInfo.render(): Info for DataFrame in the base class
SeriesInfo.render(): Info for Series in the base class
DataFrame.info(): Info for DataFrame in the constant
```

So, docstrings for ``DataFrameInfo`` and ``SeriesInfo`` work fine, as expected.
But additional redirection from ``DataFrame.info()`` was not possible due to originally empty ``__doc__`` for ``DataFrameInfo`` and ``SeriesInfo``, I suppose.

Linking previous comment on the docstrings: https://github.com/pandas-dev/pandas/pull/37320#pullrequestreview-530819895 (the reason the docstrings are to be generic for both DataFrame and Series and done via doc decorator).
This is cumbersome, better to avoid even if it was supported. If it's the same docstring make all them be based in the same base one. Also, are all these public? If they are not rendered in the docs, we probably don't care much on having tge docstring.
> 
> 
> This is cumbersome, better to avoid even if it was supported. If it's the same docstring make all them be based in the same base one. Also, are all these public? If they are not rendered in the docs, we probably don't care much on having tge docstring.

Only ``DataFrame.info()`` is public.
Just to clarify, do you consider the current approach (committed) as cumbersome?

Another option, as you already mentioned above, is to simply have different docstrings for ``DataFrame.info`` and ``Series.info``, but looks like @jreback preferred a more generic approach.
Sorry, what I meant that is cumbersome is your example, where doc is being used to copy one docstring, and then used again to copy the copy.

Not so familiar with those `DataFrameInfo` class, but if the mehods you are documenting are not public, having those docstrings it's not very useful. Docstrings are useful for two things, for developers reading the code, and for generating the docs as html. With `@doc`, we ruin the former, so we don't need to repeat long docstrings rendered in the documentation. So, if a method is not public, in my opinion is as useful to have a doctring `"""Look this other object, does the same."""` as using `@doc`.
Right, there is no need to have docstrings inside the internal classes.
But the idea was to generate docstrings for ``DataFrame.info()`` and ``Series.info()`` (to be added) from within ``pandas/io/formats/info.py``.
@ivanovmg in theory this is a good change, if you want to merge master. ideally small changes in a series of PRs are better.
@ivanovmg can you merge master again pls. and ping when green.
@jreback, I merged master.
It is green.
thanks @ivanovmg