allow type to be specified in argument of tolist()
State: closed

- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)  
  - no corresponding issue
- [x] [Tests added and passed]
  - modified `tests/indexes/test_any_index.py:TestConversion.test_tolist_matches_list()`
- [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/v2.0.0.rst` file if fixing a bug or adding a new feature.


I'm not sure how people will feel about this idea.

The idea here is that if you do something like `y = [x for x in df.index if x.startswith("a")]`, right now, the type of `x` cannot be inferred.  That means that the expression `x.startswith("a")` isn't type checked. 

But, with this PR, if you do `y = [x for x in df.index.tolist(str) if x.startswith("a")]` then  the expression `x.startswith("a")` will be type checked.  Not only that, the type checker can infer that `y` will be `list[str]` in this example.

Furthermore, if you wrote  `y = [x for x in df.index.tolist(int) if x.startswith("a")]`, then the type checker would catch that you can't call `startswith()` on an `int`.

Note that the new argument to `tolist()` is optional.  The argument is ignored in the `tolist()` method.  It is only used to change the type of the result.  It does not convert the type of the values in the list.  This is only there for convenience with typing.

If this PR is accepted for 2.0, then a corresponding change will also be made to `pandas-stubs` once 2.0 is released.


Labels:
name: Typing
description: type annotations, mypy/pyright type checking

Comments:
Not really sure about this - shouldn't all of the type information (if any) be driven by the Index itself? i.e. a more natural resolution IMO would be for a StringIndex to explicitly resolve to this type during iteration
> Not really sure about this - shouldn't all of the type information (if any) be driven by the Index itself? i.e. a more natural resolution IMO would be for a StringIndex to explicitly resolve to this type during iteration

The issue comes up more with columns.  e.g.
```python
import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.random((3,4)), columns=["cat", "dog", "rat", "pig"])
cols = [c for c in df.columns if "at" in c]
```
So you don't have that `df.columns` is a `StringIndex`
> The issue comes up more with columns. e.g.

Is there a reason why we can't (or it's just harder to) treat columns the same as index? Or it just hasn't been done yet?
> > The issue comes up more with columns. e.g.
> 
> Is there a reason why we can't (or it's just harder to) treat columns the same as index? Or it just hasn't been done yet?

`df.columns` returns an `Index`.  It could be any kind of `Index`, including `MultiIndex`.

Maybe what we should do is introduce a `filter` on `Index`, so you would have:
```python
    def filter(self, filter_func: Callable[[Hashable], bool]) -> Index:
        ...
```
Then you could write:  `new_cols = df.columns.filter(lambda i: "at" in i)` instead of `new_cols = [i for i in cols if "at" in i]`

> Maybe what we should do is introduce a `filter` on `Index`, so you would have:

And now I just discovered `DataFrame.filter()` that does this when you want to restrict the columns of a `DataFrame`, and `Index.map()` that lets you filter an index.

So I will close this.