扩展第三方库/模块的存根文件

问题描述 投票:0回答:4

我正在使用

yarl
库的
URL
对象。

它有一个准私有属性

._val
,它是一个
urllib.parse.SplitResult
对象,但
yarl/__init__.pyi
中没有类型注释。 (可以理解,如果开发人员不想正式将其纳入公共 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"

那么,在我自己的项目中,如何将实例属性注释“附加”(或扩展)到

URL
,以便它可以在我的项目的其余部分中使用? 即

from yarl import URL

URL._val: SplitResult
# ...

(mypy 也不喜欢这样;“不能在对非 self 属性的赋值中声明类型。”)

我尝试在

stubs/yarl/__init__.pyi
:

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

class URL:
    _val: SplitResult

然后按照

存根文件
中所述设置export MYPYPATH='.../stubs'。 然而,这覆盖而不是扩展现有的注释,所以一切但是
._val
都会抛出错误:

错误:“URL”没有属性“with_scheme”

错误:“URL”没有属性“主机”

错误:“URL”没有属性“片段”

...等等。

python python-3.x mypy
4个回答
4
投票

不幸的是,我认为确实没有办法对某些第 3 方库的类型提示进行“部分”更改 - 至少不能使用 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 更新/作为第三方库更新的存根而变得多余,但它是一个很好的标志,可以在一般情况下启用。

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

    如果有帮助的话,一些在 Typeshed(标准库的类型存储库)中为私有或未记录的属性添加类型提示的先例 - 请参阅其贡献指南中的 “包含内容”部分

  3. 如果库维护者不愿意添加此属性,您始终可以分叉此库的存根,对分叉的存根进行更改,然后开始使用它。

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


3
投票

不幸的是,我认为确实没有办法对某些第 3 方库的类型提示进行“部分”更改 - 至少不能使用 mypy。

其实是有的。根据 PEP 561,类型检查器“应该”查找存根的第一个位置是在

$PATH
:

  1. 手动将存根或 Python 源代码放入路径的开头。类型检查器应该提供此功能,以允许用户完全控制要使用的存根,并修补包中损坏的存根/内联类型。在 mypy 中,可以使用 $MYPYPATH 环境变量来实现此目的。

因此,用额外目录的路径列表填充

$MYPYPATH
,其中
mypy
应查找存根并将修复程序放在那里。您“应该”能够简单地使用正确的类型覆盖失败的部分。根据 mypy 文档

这些存根文件不需要完整!一个好的策略是使用 Stubgen(一个与 mypy 捆绑在一起的程序)来生成存根的第一个草稿。然后,您可以仅迭代您需要的库部分。

你“不应该”甚至必须使用

stubgen
,但请尝试一下(如果你需要包中的其他类型提示,你可能必须使用
stubgen
,尽管我不确定)。即使您这样做,最坏的情况下,请在文件上运行
stubgen
并覆盖存根中损坏的部分。


2
投票

一种选择是根据您想要“扩展”的类创建一个新类。当我想要自动完成我正在使用的数据时,我会对 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
类实际上并未被使用(作为类),它仅用作类型(因此在运行时被忽略)。


1
投票

一种可能性是简单地忽略此分配的

u
的类型:

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

mypy
会假设您知道自己在做什么,并且
u
有一个类型为
_val
str
属性。

© www.soinside.com 2019 - 2024. All rights reserved.