如何区分两种动态类型?

How can I distinguish between two dynamic types?

我正在对一个使用 pandas 的项目进行类型检查。 Pandas 不包含类型注释并且类型流中没有存根文件。

如我所料,mypy 为这个简单示例引发错误:

class A:
    def method(self) -> int:
        return 1


class B(A):
    def method(self) -> float:
        return 1.1
$ mypy mypy_example.py 
mypy_example.py:11: error: Return type "float" of "method" incompatible with return type "int" in supertype "A"

考虑以下示例:

class C:
    def method(self) -> pd.Series:
        return pd.Series([1, 2, 3])


class D(C):
    def method(self) -> pd.DataFrame:
        return pd.DataFrame({"a": [1, 2, 3]})

不出所料,mypy 说找不到 pandas 的存根文件,所以它没有找到错误。

$ mypy mypy_example.py 
mypy_example.py:1: error: No library stub file for module 'pandas'
mypy_example.py:1: note: (Stub files are from https://github.com/python/typeshed)
mypy_example.py:11: error: Return type "float" of "method" incompatible with return type "int" in supertype "A"

我可以设置 ignore_missing_imports,但这意味着我错过了我想要捕获的错误。

我在存根文件中尝试了一些东西但没有成功:

from typing import Any, NewType

# dynamic typing, but doesn't discriminate between Series and DataFrame
Series = Any
DataFrame = Any

# discriminates but doesn't dynamically type
Series = NewType('Series', object)
DataFrame = NewType('DataFrame', object)

是否可以编写一个简短的存根文件或类型注释,使我能够利用动态类型但认识到 pd.Seriespd.DataFrame 是不同的类型?

而不是试图让 mypy 区分两个动态 classes,我实际上会采取使它们 not 动态(或者更确切地说,仅部分 动态)通过将它们定义为完全成熟的 classes 在你的存根中。

您可以通过将您的两个 class 定义为如下所示,从一组非常初步和最小的存根开始:

from typing import Any

class Series:
    def __init__(self, *args: Any, **kwargs: Any) -> None: ...
    def __getattr__(self, name: str) -> Any: ...

class DataFrame(Series):
    def __init__(self, *args: Any, **kwargs: Any) -> None: ...
    def __getattr__(self, name: str) -> Any: ...

__getattr__ 函数让 mypy 明白你的 class 是不完整的,没有完全注释。这意味着像 DataFrame().query(...) 这样的事情将继续进行类型检查,即使该功能从未明确添加到您的 class.

当然,如果您确实决定添加一些方法签名,mypy 将开始对这些调用进行类型检查,而不是让它们动态键入。这意味着您还可以根据需要逐步添加更精确的方法签名,并可能最终完全摆脱 __getattr__

如果您决定走这条路,您可能会发现现有的 numpy stubs to be a good source of inspiration. And if you want really precise types, the discussion here 可能是相关的。

writing incomplete stubs 上的 typeshed 指南提供了有关编写部分存根的更多信息,如果您有兴趣的话。