如何将泛型类类型与函数返回类型结合使用?
为什么这个类型提示不起作用呢?
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 个回答
我原本以为,创建一个指定类型为
str
的Base
实例时,它应该能与test()
的返回值兼容,因为它会转换成Test[str](t)
。
你写的 b = Base[str]()
这一行对 class Base(Generic[V]):
的定义没有任何影响。Base.test
的定义会因为 t
是 str
(通过 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)]
是不合理的,因为返回类型并不是受限类型之一(只能是 int
或 str
,而不是 V: ...
尝试做的事情)。更不用说,如果没有 class
前缀,Test
被 pyright 解释为后面的 Test[V: (str, int)]
是一个 slice
操作,这在这个上下文中也是没有意义的。至于 test2
,V
在那个上下文中并没有绑定到具体类型——class
定义中的 V
只是一个占位符,用于表示某个具体类型,这个具体类型必须在函数返回类型的上下文中定义为 int
或 str
,所以再次无效。
根据问题中给出的代码,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)
现在 b
和 c
是 Base
类的实例,具有有效的类型限制,如果参数 t
被替换为另一种类型(例如,把 1
和 '1'
互换,或者甚至是无效类型),就会出现类型验证错误,因为类型 V
会被限制在 Base
的实例化中,test
方法也只能是 str
或 int
。