【问题标题】:Extending stub file for a third-party library/module为第三方库/模块扩展存根文件
【发布时间】:2019-09-17 13:30:07
【问题描述】:

我正在使用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

然后按照stub files 中的描述设置export MYPYPATH='.../stubs'。然而,这覆盖,而不是扩展,现有的注释,所以一切 ._val 抛出和错误:

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

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

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

...等等。

【问题讨论】:

    标签: python python-3.x mypy


    【解决方案1】:

    不幸的是,我认为实际上没有办法对某些 3rd 方库的类型提示进行“部分”更改——至少,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(标准库的类型存储库)中添加私有或未记录的属性 - 请参阅 "What to include" section in their contribution guidelines

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

    我会先亲自尝试解决方案 2,然后再尝试解决方案 1,但这只是我自己。

    【讨论】:

    • 对于它的价值,yarl 开发人员chose to go against (2) 中的哲学,但对于它的价值,我认为双方的论点都有优点
    • 为了提供更多关于为什么部分更改没有按预期工作的背景信息,Guido 提供了 commented here,表示支持部分覆盖 at the file level, not at the function levelWe have no intention to support the latter
    【解决方案2】:

    一种可能性是简单地忽略此分配的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】:

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

      【讨论】:

        猜你喜欢
        • 2017-06-22
        • 2017-09-25
        • 2018-06-21
        • 2018-03-11
        • 2018-01-12
        • 1970-01-01
        • 2020-05-20
        • 2015-09-20
        • 2015-09-03
        相关资源
        最近更新 更多