语境

我们开发了一个 Python 库,其中包含一个需要 numlike 参数的函数。我们在签名中指定这一点并使用 python 类型提示:

def cool(value: float | int | List[float | int]) 

🏳 问题​​与目标

在运行时,我们注意到传递 numpy 数字类型也很好,例如np.float16(1.2345)。所以我们想:为什么不将“numpy 数字类型”合并到我们的签名中,因为这对使用我们库的社区是有益的。

但是,我们不希望numpy在我们的项目中存在依赖关系。我们只想在方法签名中表示我们可以采用floatint、它们的列表或任何“numpy 数字类型”。如果用户尚未numpy在他们的系统上安装,他们仍然应该能够使用我们的库,并且忽略他们也可能传递“numpy 数字类型”。

我们不想依赖它,numpy因为我们不在我们的库中使用它(除了在我们的方法签名中允许它们的类型)。那么为什么要把它包含在我们的依赖图中呢?没有理由这样做。少一种依赖就更好了。

附加要求/背景

  • 我们寻找与所有 Python 版本兼容的答案>=3.8
  • (答案应该适用于setuptools>=69.0。)
  • 答案应该是这样的:当我们在 IDE(例如 VSCode)中Ctrl + Space输入时,我们可以获得正确的 IntelliSense () 。cool(
  • 就是我们的pyproject.toml样子。

努力

  • [project.optional-dependencies]我们注意到该文件的选项pyproject.toml,请参见。但是,目前尚不清楚此可选依赖项声明如何帮助我们numpy在方法签名中提供可选数据类型。
  • numpy提供类型注释。是否有可能只依赖这个子包?
  • 我们在搜索引擎上进行了搜索并发现了,但是我们的问题更具体的是我们如何只能使用另一个模块中的类型。我们还发现了可选的numpy 类型无关。

3

  • 1
    运行类型检查时只能有条件地导入 numpy 吗?换句话说,您可以使用if TYPE_CHECKING:避免循环导入的技巧吗?


    – 

  • 但实际问题是:你为什么不想包括numpy?当然,它很“大”,但只是对于 Python 包来说才大。从绝对意义上讲,就下载量和文件系统占用量而言,它基本上是非常小的。


    – 


  • @Mike’Pomax’Kamermans 当然,它的尺寸可能很小。然而,它仍然是我们作为库的维护者必须处理的依赖项,例如更新它的版本。我们没有任何使用 numpy 的代码行,那么为什么我们的库的安装应该包括在用户计算机上安装 numpy 的步骤呢?


    – 


1 个回答
1

推迟对注释的评估,并且仅有导入 numpy 。

from __future__ import annotations
import typing as t

if t.TYPE_CHECKING:
    import numpy as np

def cool(value: int | np.floating | etc ...):
    ...

现在,仅在类型检查时才需要 numpy 依赖项。

请参阅

附带问题..

numpy提供numpy.typing类型注释。是否有可能只依赖这个子包?

不,这是不可能的。

[project.optional-dependencies]我们注意到该文件的选项pyproject.toml

它在这里对你没有多大帮助。如果您想要用户可以选择加入的“额外”依赖项,它仍然很有用,例如:

pip install mypkg          # install with required dependencies
pip install mypkg[typing]  # install with extra dependencies such as numpy

例如,然后您可以使用它轻松安装该软件包以及 CI 中的软件依赖。

10

  • 不过,使用numbers.Number会得到像decimal.Decimal和 之类的东西fractions.Fraction。 (它也会出现complex– 你需要numbers.Real排除它。)


    – 

  • decimal.Decimal不太可能没事。即使只是尝试添加0.5到 adecimal.Decimal也会产生TypeError.numbers.Number如果不知道更具体的类型,您对实例几乎无能为力。


    – 

  • 不幸的是,对于选项 1,(以及 VSCode 中的 Pylance 语言服务器)给出了以下错误:error: Argument of type "float16" cannot be assigned to parameter "param" of type "Real" in function "blablah". "float16" is incompatible with "Real" (reportArgumentType)


    – 

  • Pyright 存储库中写下了这个问题


    – 

  • 1
    或多或少评论让我意识到选项 1 不可行。 intfloat实际上并不从numbers类型继承,这些是虚拟子类,并且 isinstance 检查在运行时动态处理。静态类型检查器无法识别虚拟子类关系,并且没有计划在 mypy 或 Pyright 中支持这一点,也没有计划在 typeshed 中覆盖它。因此,数字塔numbersABC 仅对运行时类型检查器有用,对使用静态分析的类型检查器不起作用。


    –