为什么 IntelliSense 不能与 pandas pipe() 一起使用?

Why won't IntelliSense work with pandas pipe()?

在 VScode 中,Intellisense 似乎无法推断出对 pandas.DataFrame.pipe 的 return 调用类型。这是一些不便的根源,因为我在使用管道后不能依赖自动完成。但是我没有在任何地方看到这个问题,所以这让我想知道是不是只有我一个人,或者我是否遗漏了什么。

我就是这样做的:

import pandas as pd
df = pd.DataFrame({'A': [1,2,3]})
df2 = df.pipe(lambda x: x + 1)

VSCode 将 df 识别为 DataFrame:, but has no clue what df2 might be:

第一个想法是,这是由于 lambda 函数中缺少类型提示。但是,如果我尝试这样做:

def add_one(df: pd.DataFrame) -> pd.DataFrame:
  return df + 1
df3 = df.pipe(add_one)

IntelliSense 仍然无法猜测 df3 的类型:

当然,作为最后的手段,我可​​以给 df3 本身添加一个提示:

df3: pd.DataFrame = df.pipe(add_one)

但似乎没有必要。 IntelliSense 似乎非常有能力推断其他复杂场景中的 return 类型,例如涉及 map:


更新:

我进行了更多试验,发现了一些有趣的模式,这些模式缩小了可能原因的范围。

我对 Pylance 不够熟悉,无法真正理解为什么会发生这种情况,但这是我的发现:

发现 1

如果导入 pandas.core.common.pipe 就会发生这种情况。 (我知道 pd.DataFrame.pipe 调用 pandas.core.generic.pipe,但内部调用 pandas.core.common.pipe,我可以在 pandas.core.common.pipe 中重现该问题。)

发现 2

如果我从 pandas.core.common 复制相同函数的定义,连同 Callable 和 TypeVar 的相关导入,并将 T 声明为 TypeVar('T'),IntelliSense 实际上会发挥它的魔力.

(实际上在pandas.core.common中,T没有被定义为TypeVar('T'),而是从pandas._typing中导入的,它被定义为TypeVar('T')。如果我导入它而不是自己定义它,它仍然可以正常工作。)

据此我很想得出结论 pandas 做的一切都是正确的,但是 Pylance 出于某种未知原因未能跟踪类型信息...

发现 3

如果我只是将 pandas.core.common 复制到本地文件 pandascommon.py 并从中导入管道,它也可以正常工作!

我也在vscode中模拟了一下,发现确实存在这个问题。我想可能和pipe()方法中的return值定义有关。我在GitHub上提交了问题,希望有所收获。

我明白了!

这是由于 Pylance 附带的存根。具体在 ~/.vscode/extensions/ms-python.vscode-pylance-2022.3.2/dist/bundled/stubs/pandas/.

例如在 core/common.pyi 中我发现了这个存根: def pipe(obj, func, *args, **kwargs): ...

Pylance 使用这个而不是 pandas.core.common.pipe 中的注释,导致了这个问题。

一个 heavy-handed 解决方案是删除(或重命名)该文件夹中的 pandas 存根。然后管道再次工作。另一方面,它破坏了其他一些东西,例如 read_csv 不再被正确推断为 return 一个 DataFrame。我认为更好的长 运行 解决方案是让 Pylance 维护者改进这些存根...

原始 pipe 问题的微创解决方案是按以下方式编辑 ~/.vscode/extensions/ms-python.vscode-pylance-2022.3.2/dist/bundled/stubs/pandas/core/frame.pyi

  • 添加from pandas._typing import T

  • 将以def pipe开头的行替换为:

    def pipe(self, func: Callable[..., T], *args, **kwargs) -> T: ...