如何将泛型类类型与函数返回类型结合使用?

1 投票
1 回答
75 浏览
提问于 2025-04-14 15:29

为什么这个类型提示不起作用呢?

from typing import Generic, TypeVar
from dataclasses import dataclass


V = TypeVar('V', int, str)


@dataclass
class Test(Generic[V]):
    a: V


class Base(Generic[V]):
    def test(self) -> Test[V]:
        t = '1'
        return Test[V](t) 


b = Base[str]()
b.test()

mypy 显示:

test.py:16: error: Argument 1 to "Test" has incompatible type "str"; expected "int"  [arg-type]

我本来期待的是,当我用指定的类型 str 创建 Base 的实例时,它应该能用于 V,这样在 test() 的返回值中也应该是兼容的,因为它会转换成 Test[str](t)

1 个回答

2

我原本以为,创建一个指定类型为 strBase 实例时,它应该能与 test() 的返回值兼容,因为它会转换成 Test[str](t)

你写的 b = Base[str]() 这一行对 class Base(Generic[V]): 的定义没有任何影响。Base.test 的定义会因为 tstr(通过 t = '1')而导致类型检查失败,所以返回的是 Test[str],而不是通用的 Test[V]。这并不是返回“混合”类型的方法(可以参考 typing.AnyStr 的例子,文档中有讨论)。如果把 t 改成 t = 1,并把 b = Base[int](),那么 mypy 会抱怨相反的问题:

error: Argument 1 to "Test" has incompatible type "int"; expected "str"  [arg-type]

使用 pyright 实际上会给出更好的错误信息:

  /tmp/so_78174062.py:16:24 - error: Argument of type "Literal['1']"
  cannot be assigned to parameter "a" of type "V@Base" in function "__init__"
    Type "Literal['1']" cannot be assigned to type "V@Base" (reportArgumentType)

这清楚地指出了这两种类型是不同的(一个是 str,另一个是 TypeVar(类型变量))。

而且,使用在 PEP 695 中引入的语法,实际上可以消除一些混淆(具体细节请参考 PEP);用这种语法重新格式化你的示例代码可能会更清楚你的意图是什么:

@dataclass
class Test[V: (str, int)]:
    a: V

class Base[V: (str, int)]:
    def test(self) -> Test[V: (str, int)]:
        t = 1
        return Test[V: (str, int)](t)

    # or alternatively
    def test2(self) -> Test[V]:
        t = 1
        return Test[V](t)

在这种情况下,定义返回类型 Test[V: (str, int)] 是不合理的,因为返回类型并不是受限类型之一(只能是 intstr,而不是 V: ... 尝试做的事情)。更不用说,如果没有 class 前缀,Test 被 pyright 解释为后面的 Test[V: (str, int)] 是一个 slice 操作,这在这个上下文中也是没有意义的。至于 test2V 在那个上下文中并没有绑定到具体类型——class 定义中的 V 只是一个占位符,用于表示某个具体类型,这个具体类型必须在函数返回类型的上下文中定义为 intstr,所以再次无效。

根据问题中给出的代码,test 方法的签名应该是这样的:

    def test(self) -> Test[str]:
        t = '1'
        return Test[str](t)

以上代码可以正确通过类型检查。


我注意到,上面的内容并没有真正回答问题的标题:“如何将通用类类型与函数返回类型结合使用?”

因为上面展示了 TypeVar 实际上是一个类型变量,所以我们可以在允许类型的地方使用它。考虑以下内容:

class Base[V: (str, int)]:
    def test(self, t: V) -> Test[V]:
        return Test[V](t)

b = Base[str]()
b.test('1')
c = Base[int]()
c.test(1)

现在 bcBase 类的实例,具有有效的类型限制,如果参数 t 被替换为另一种类型(例如,把 1'1' 互换,或者甚至是无效类型),就会出现类型验证错误,因为类型 V 会被限制在 Base 的实例化中,test 方法也只能是 strint

撰写回答