pandas: `fillna()`など`inplace`引数があるメソッドの戻り値に対して`[key]`で情報を取得すると、Pylintの`unsubscriptable-object`エラーが発生する

# 環境
* Python 3.12.1
* pylint 3.1.0


# 何が起きたのか
以下のコードは、`pandas.DataFrame`に対して`[column]`で列の情報を取得しています。
pylintでコードをチェックしたところ、`E1136`というエラーが発生しました。


```python:sample1.py
import pandas as pd

df = pd.DataFrame({"a": [1, None], "b": [None, 4]})
# OK
print(df["a"])

df2 = df.fillna(0)
# error:  Value 'df2' is unsubscriptable
print(df2["a"])
```


```
$ pylint sample1.py --disable W,C
************* Module sample1
sample1.py:9:6: E1136: Value 'df2' is unsubscriptable (unsubscriptable-object)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)
```

変数`df`と`df2`の型はどちらも`pandas.DataFrame`です。しかし、`fillna()`の戻り値である`df2`に対して`[column]`で列の情報を取得したときのみ、"Value 'df2' is unsubscriptable"というエラーが発生しました。

https://pylint.readthedocs.io/en/stable/user_guide/messages/error/unsubscriptable-object.html

# 検証したこと

## `fillna()`の戻り値に対して`[column]`に代入する

```python:sample2.py
import pandas as pd

df = pd.DataFrame({"a": [1, None], "b": [None, 4]})

df2 = df.fillna(0)
# error:  'df2' does not support item assignment
df2["c"] = 0
```

```
$ pylint sample2.py --disable C,W
************* Module sample2
sample2.py:7:0: E1137: 'df2' does not support item assignment (unsupported-assignment-operation)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)
```

`E1137`エラーが発生しました。

https://pylint.readthedocs.io/en/latest/user_guide/messages/error/unsupported-assignment-operation.html


## `pandas.Sereis`で試す

```python:sample3.py
import pandas as pd

s = pd.Series([1,2], index=["a","b"])
# OK
print(s["a"])

s2 = s.fillna(0)
# error:  Value 's2' is unsubscriptable
print(s2["a"])
```


```
$ pylint sample3.py --disable C,W
************* Module sample3
sample3.py:9:6: E1136: Value 's2' is unsubscriptable (unsubscriptable-object)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)
```

`pandas.Series`でも`E1137`エラーが発生しました。


## fillna以外のメソッドを試す


```python:sample4.py
import pandas as pd

df = pd.DataFrame({"a": [1, None], "b": [None, 4]})

df2 = df.drop("a")
# error:  Value 'df2' is unsubscriptable
print(df2["a"])

df3 = df.reset_index()
# error:  Value 'df3' is unsubscriptable
print(df3["a"])

df4 = df.replace({1:111})
# error:  Value 'df4' is unsubscriptable
print(df4["a"])

df5 = df + df
# OK
print(df5["a"])

df6 = df.add(df)
# OK
print(df6["a"])

df7 = df.isna()
# OK
print(df7["a"])

```


```
1$ pylint sample4.py --disable W,C
************* Module sample4
sample4.py:7:6: E1136: Value 'df2' is unsubscriptable (unsubscriptable-object)
sample4.py:11:6: E1136: Value 'df3' is unsubscriptable (unsubscriptable-object)
sample4.py:15:6: E1136: Value 'df4' is unsubscriptable (unsubscriptable-object)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)
```


`drop()`や`reset_index()`でも同様の`E1136`エラーが発生しました。
しかし、`isna()`や`add()`ではエラーは発生しませんでした。

## `fillna()`の戻り値に対して`[column]`以外で情報を取得する。


```python:sample5.py
import pandas as pd

df = pd.DataFrame({"a": [1, None], "b": [None, 4]})
df2 = df.fillna(0)
# OK
print(df2.loc[:, "a"])

# OK
print(df2.fillna(0))

# OK
print(len(df2))
```


```
$ pylint sample5.py --disable W,C

-------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 6.67/10, +3.33)
```

pylintのエラーは発生しませんでした。

`DataFrame`クラスには`__len__()`が定義されているので、`len(df2)`もエラーが発生すると思ったのですが、エラーは発生しませんでした。

https://github.com/pandas-dev/pandas/blob/59f6a3373751bd6b8e257066e33fdc3c618030ea/pandas/core/frame.py#L1594-L1598

# 考察
エラーが発生した`fillna()`, `drop()`, `reset_index()`メソッドには、`inplace`引数が存在します。このことから、`inplace`引数が存在するメソッドの戻り値に対して大括弧`[]`で情報を取得または代入する際に、pylintでエラーが発生すると考えました。

なお、`fillna()`は`overload`デコレータを使って定義されています。

```python:pandas/core/generic.py
    @overload
    def fillna(
        self,
        value: Hashable | Mapping | Series | DataFrame,
        *,
        axis: Axis | None = ...,
        inplace: Literal[False] = ...,
        limit: int | None = ...,
    ) -> Self: ...

    @overload
    def fillna(
        self,
        value: Hashable | Mapping | Series | DataFrame,
        *,
        axis: Axis | None = ...,
        inplace: Literal[True],
        limit: int | None = ...,
    ) -> None: ...

    @overload
    def fillna(
        self,
        value: Hashable | Mapping | Series | DataFrame,
        *,
        axis: Axis | None = ...,
        inplace: bool = ...,
        limit: int | None = ...,
    ) -> Self | None: ...
```

https://github.com/pandas-dev/pandas/blob/main/pandas/core/generic.py#L6804-L6832

以下の`sample11.py`では、`overload`デコレータを使って、`fillna()`のシグネチャに似たメソッド`Foo.get2()`を定義しました。
`sample11.py`をpylintでチェックすると、`Foo.get2()`の戻り値に対して`E1136`エラーが発生しました。
また、`Foo.get3()`の中身は`Foo.get2()`と同じですが、`overload`デコレータを利用していません。`Foo.get3()`の戻り値に対しては`E1136`エラーは発生しませんでした。


```python:sample11.py
from typing import Self, overload, Literal


class Foo:
    def __init__(self, data: dict[str, int]) -> None:
        self.data = data

    def __getitem__(self, key: str) -> int:
        return self.data[key]

    def get1(self) -> Self:
        return self

    @overload
    def get2(
        self,
        *,
        inplace: Literal[False],
    ) -> Self: ...

    @overload
    def get2(
        self,
        *,
        inplace: Literal[True],
    ) -> None: ...

    def get2(
        self,
        *,
        inplace: bool = False,
    ) -> Self | None:
        if inplace:
            self.data = {key: value * 2 for key, value in self.data.items()}
            return None
        return self.__class__({key: value * 2 for key, value in self.data.items()})

    def get3(
        self,
        *,
        inplace: bool = False,
    ) -> Self | None:
        if inplace:
            self.data = {key: value * 2 for key, value in self.data.items()}
            return None
        return self.__class__({key: value * 2 for key, value in self.data.items()})


f = Foo({"a": 1})
# OK
print(f["a"])
f1 = f.get1()
# OK
print(f1["a"])

f2 = f.get2(inplace=False)
# error: Value 'f2' is unsubscriptable
print(f2["a"])

f3 = f.get3(inplace=False)
# OK
print(f3["a"])
```

```
$ pylint sample11.py --disable W,C
************* Module sample11
sample11.py:58:6: E1136: Value 'f2' is unsubscriptable (unsubscriptable-object)

------------------------------------------------------------------
Your code has been rated at 8.33/10 (previous run: 7.83/10, +0.51)

```

以上のことから、pylintは`overload`デコレータを正しく認識できないようです。


pylintのissueを見ると、

> '@overload' is not yet supported.

とのコメントがありました。

https://github.com/pylint-dev/pylint/issues/9474#issuecomment-1972223725



# まとめ
以下、分かったことです。

* pandasの`fillna()`や`drop()`など`inplace`引数を持つメソッドの戻り値に対して、大括弧`[]`で情報を取得する（`__getitem__()`）と、pylintの`E1136`が発生する
    * 大括弧`[]`で情報を設定する（`__setitem__()`）と`E1137`エラーが発生する
* `fillna()`などは`overload`デコレータで定義されている。pylintは`overload`デコレータに対応していない。したがって、`__getitem__`/`__setitem__`は実装されているのにも関わらず、`E1136`,`E1137`エラーが発生するのかもしれない


以下、分からなかったことです。
* pandasの`fillna()`の戻り値に対して、`len()`を実行しても（`DataFrame`クラスには`__len__()`が定義されている）pylintでエラーが発生しなかった。なぜか？
* `overload`デコレータでメソッドを定義すると、なぜpylintの`E1136や`E1137`のエラーが発生するのか？



# 補足

## `inplace=True`を指定するのは非推奨

`fillna()`に`inpace=True`を指定すれば、pylintの`のE1136`エラーを回避できます。

```python:sample.py
import pandas as pd

df = pd.DataFrame({"a": [1, None], "b": [None, 4]})
# pylint
のE1136 エラーを回避するため、`inplace=True`を指定
# df = df.fillna(0)
df.fillna(0, inplace=True)
print(df["a"])
```

しかし、`inplace=True`を指定すると、Ruffの`pandas-use-of-inplace-argument`に引っ掛かります。

https://docs.astral.sh/ruff/rules/pandas-use-of-inplace-argument/

`inplace=True`を指定してもパフォーマンス上の利点はないようです。したがって、`inplace=False`を指定してpylintのエラーを無視する方が良いでしょう。

## Ruffによるpylintのルールの実装
Ruffはpylintのルールを徐々に実装しています。

https://github.com/astral-sh/ruff/issues/970

2024/05/01時点では、`E1136`, `E1137`は実装されていません。
これらのルールが実装されたとき、今回のような挙動になるのかどうかが気になります。


### 参考にしたサイト

https://github.com/pylint-dev/pylint/issues/6942

https://github.com/pylint-dev/pylint/issues/3637