python 中类型变量和类型联合不兼容吗?

zap*_*zap 6 python type-hinting type-variables pyright

考虑以下片段:

from typing import TypeVar

import numpy as np

T = TypeVar("T", float, np.ndarray)


def f(x: T) -> T:
    """
    expects a float or an array and returns an output of the same type
    """
    return x * 2


f(1)  # ok

f(np.array([1, 2, 3]))  # ok


def g(x: float | np.ndarray) -> float | np.ndarray:
    """
    expects either a float or an array
    """
    return f(x) / 2  # should be fine, but pyright complains about type
Run Code Online (Sandbox Code Playgroud)

我创建了一个 TypeVar 来提示f需要输入浮点数或数组,并将返回相同类型的输出。

中的类型提示g更加宽松。它需要一个浮点或一个数组,并将返回一个浮点或一个数组,而不将输出的类型限制为输入的类型。

直观上,这个设置是有道理的。在函数的定义中,g我们知道我们期望x是浮点数或数组,即f期望作为输入的内容。然而,当我转到x最后f一行时,Pyright 抱怨道:

类型“float | ndarray[Unknown, Unknown]”的参数不能分配给函数“f”中类型“T@f”的参数“x”

类型“float | ndarray[Unknown, Unknown]”与约束类型变量“T”不兼容

f这是令人惊讶和令人沮丧的,因为这意味着如果不非常谨慎地编写类型提示的方式,就无法使用我的函数。

关于如何解决这个问题有什么想法吗?

编辑:在 Brian61354270 发表评论后,我重新创建了基本上相同的示例,只是不依赖 numpy。这里我们使用:而不是 numpy 数组Fraction

from fractions import Fraction
from typing import TypeVar

T = TypeVar("T", float, Fraction)


def f(x: T) -> T:
    """
    expects a float or a Fraction and returns an output of the same type
    """
    return x * 2


f(1.0)  # ok

f(Fraction(1, 2))  # ok


def g(x: float | Fraction) -> float | Fraction:
    """
    expects either a float or a Fraction
    """
    return f(x) / 2  # should be fine, but pyright complains about type
Run Code Online (Sandbox Code Playgroud)

Pyright 再次报告了本质上相同的问题:

“float | Fraction”类型的参数不能分配给函数“f”中“T@f”类型的参数“x”

类型“float | Fraction”与约束类型变量“T”不兼容

有趣的是,如果我们不Fraction使用int,类型检查就会通过:

from typing import TypeVar

T = TypeVar("T", float, int)


def f(x: T) -> T:
    """
    expects a float or an integer and returns an output of the same type
    """
    return x * 2


f(1.0)  # ok

f(1)  # ok


def g(x: float | int) -> float | int:
    """
    expects either a float or an integer
    """
    return f(x) / 2  # now its ok

Run Code Online (Sandbox Code Playgroud)

Bri*_*ian 0

虽然不是理想的解决方案,但您可以通过在显式类型保护下重复调用fin来解决此错误:g

def g(x: float | np.ndarray) -> float | np.ndarray:
    """
    expects either a float or an array
    """
    if isinstance(x, (float, int)):
        return f(x) / 2
    return f(x) / 2
Run Code Online (Sandbox Code Playgroud)

使用pyright v1.1.329输出:

0 errors, 0 warnings, 0 informations 
Run Code Online (Sandbox Code Playgroud)

  • 不错的收获。然而,这确实是人为的,我认为这不是一个可接受的解决方案。想象一下,你非常努力地为你的库编写良好的类型提示,结果却迫使你的用户编写这样的代码!这将是一场灾难。我认为类型命中是为了帮助我们编写更清晰的代码,但我担心他们开始强制执行奇怪的编码模式,只是为了欺骗类型检查器通过测试。 (2认同)