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”没有属性“片段”
...等等。
不幸的是,我认为确实没有办法对某些第 3 方库的类型提示进行“部分”更改 - 至少不能使用 mypy。
我会尝试以下三个选项之一:
只需
# 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 更新/作为第三方库更新的存根而变得多余,但它是一个很好的标志,可以在一般情况下启用。与该库的维护者交谈,看看他们是否愿意为此属性添加类型提示(即使它是私有的),或者通过一些新的 API 公开此信息。
如果有帮助的话,有一些在 Typeshed(标准库的类型存储库)中为私有或未记录的属性添加类型提示的先例 - 请参阅其贡献指南中的 “包含内容”部分。
如果库维护者不愿意添加此属性,您始终可以分叉此库的存根,对分叉的存根进行更改,然后开始使用它。
我个人会先尝试解决方案 2,然后再尝试解决方案 1,但这只是我的想法。
不幸的是,我认为确实没有办法对某些第 3 方库的类型提示进行“部分”更改 - 至少不能使用 mypy。
其实是有的。根据 PEP 561,类型检查器“应该”查找存根的第一个位置是在
$PATH
:
- 手动将存根或 Python 源代码放入路径的开头。类型检查器应该提供此功能,以允许用户完全控制要使用的存根,并修补包中损坏的存根/内联类型。在 mypy 中,可以使用 $MYPYPATH 环境变量来实现此目的。
因此,用额外目录的路径列表填充
$MYPYPATH
,其中mypy
应查找存根并将修复程序放在那里。您“应该”能够简单地使用正确的类型覆盖失败的部分。根据 mypy 文档:
这些存根文件不需要完整!一个好的策略是使用 Stubgen(一个与 mypy 捆绑在一起的程序)来生成存根的第一个草稿。然后,您可以仅迭代您需要的库部分。
你“不应该”甚至必须使用
stubgen
,但请尝试一下(如果你需要包中的其他类型提示,你可能必须使用stubgen
,尽管我不确定)。即使您这样做,最坏的情况下,请在文件上运行 stubgen
并覆盖存根中损坏的部分。
一种选择是根据您想要“扩展”的类创建一个新类。当我想要自动完成我正在使用的数据时,我会对 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
类实际上并未被使用(作为类),它仅用作类型(因此在运行时被忽略)。
一种可能性是简单地忽略此分配的
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
属性。