为第三方扩展存根文件 library/module

Extending stub file for a third-party library/module

我正在使用 yarl 库的 URL 对象。

它有一个准私有属性,._val,它是一个 urllib.parse.SplitResult 对象,但在 yarl/__init__.pyi 中没有类型注释。 (可以理解,如果开发者不想正式将其作为 public API 的一部分。)

但是,我选择使用 URL._val 风险自负。一个虚拟的例子:

# urltest.py
from urllib.parse import SplitResult
from typing import Tuple

from yarl import URL


def foo(u: URL) -> Tuple[str, str, str]:
    sr: SplitResult = u._val
    return sr[:3]

但是mypy不喜欢这样,因为它抱怨:

$ mypy urltest.py
"URL" has no attribute "_val"

那么,在我自己的项目中,我如何才能 "tack on"(或扩展)实例属性注释到 URL,以便它可以在我的项目的其余部分中使用?即

from yarl import URL

URL._val: SplitResult
# ...

(mypy也不喜欢这样;"Type cannot be declared in assignment to non-self attribute.")


更新

我尝试在 stubs/yarl/__init__.pyi:

中创建一个新的存根文件
from urllib.parse import SplitResult

class URL:
    _val: SplitResult

然后按照stub files中的描述设置export MYPYPATH='.../stubs'。但是,此 覆盖,而不是扩展, 现有注释,因此 ._val 的所有内容都会抛出错误:

error: "URL" has no attribute "with_scheme"

error: "URL" has no attribute "host"

error: "URL" has no attribute "fragment"

...等等。

一种可能性是简单地忽略此赋值的 u 类型:

def foo(u: URL) -> Tuple[str, str, str, str]:
    sr: SplitResult = typing.cast(typing.Any, u)._val
    return sr[:3]

mypy 将假定您知道自己在做什么,并且 u 具有类型为 str.

_val 属性

不幸的是,我不认为真的有办法对某些第 3 方库的类型提示进行 "partial" 更改——至少,mypy 不行。

我会尝试以下三个选项之一:

  1. 只需# type: ignore属性访问:

    def foo(u: URL) -> Tuple[str, str, str]:
        sr: SplitResult = u._val  # type: ignore
        return sr[:3]
    

    此类型忽略将抑制在该行上生成的任何错误消息。如果您打算采用这种方法,我还建议使用带有 --warn-unused-ignores 标志的 运行 mypy,它将报告任何冗余和未使用的 # type: ignore 语句。这个特定的 # type: ignore 不太可能会变得多余,因为 mypy updates/as 是第三方库更新的存根,但它是一个很好的标志,可以在一般情况下启用。

  2. 与这个库的维护者谈谈,看看他们是否愿意为此属性添加类型提示(即使它是私有的),或者通过一些新的 API.

    如果有帮助,一些先例,可以在 Typeshed(标准库的类型存储库)中为私有或未记录的属性添加类型提示——请参阅 "What to include" section in their contribution guidelines.

  3. 如果库维护者不愿意添加这个属性,你总是可以只为这个库分叉存根,对分叉的存根进行更改,然后开始使用它。

我个人会先尝试解决方案 2,然后再尝试解决方案 1,但这就是我。

一个选项是根据您想要 'extend' 的 class 创建一个新的 class。当我想要自动完成我正在使用的数据时,我为 Pandas DataFrame 对象执行此操作。

import pandas as pd

class TitanicDataFrame(pd.DataFrame):
    PassengerId: pd.Series
    Survived: pd.Series
    Name: pd.Series
    Sex: pd.Series
    Age: pd.Series


df: TitanicDataFrame = pd.read_csv('data/titanic.csv')
mean_age = df.Age.mean()

请注意 TitanicDataFrame class 实际上并未使用(作为 class),它仅用作类型(因此在运行时被忽略)。