TypeVar 的解释
如果您完全不熟悉 Python 中的类型注释,我之前的文章应该可以帮助您入门。
在这篇文章中,我将向您展示如何使用类型变量(或TypeVar
s)来获得乐趣和利益。
问题
此函数接受任何参数并按原样返回。如何向类型检查器解释返回类型与 的类型相同arg
?
def identity(arg):
return arg
为什么不使用Any
?
def identity(arg: Any) -> Any:
return arg
如果使用Any
,类型检查器将无法理解此函数的工作原理:就其本身而言,该函数可以返回任何值。返回类型与 的类型无关arg
。
我们确实希望number
在int
这里,以便类型检查器能够在下一行捕获错误:
为什么不针对不同类型专门设计函数?
def identity_int(arg: int) -> int:
return arg
def identity_str(arg: str) -> str:
return arg
def identity_list_str(arg: list[str]) -> list[str]:
return arg
...
-
这扩展性不好。你要重复同样的功能 10 次吗?你会记得保持同步吗?
-
如果这是一个库函数怎么办?你无法预测人们会如何使用这个函数。
解决方案:类型变量
类型变量允许你将多个类型链接在一起。你可以这样使用类型变量来注释identity
函数:
from typing import TypeVar
T = TypeVar("T")
def identity(arg: T) -> T:
return arg
这里返回类型与参数类型“链接”在一起:无论你把什么输入到函数中,都会得到相同的结果。
实际运行效果如下(在 VSCode 中使用 Pylance):
对类型变量施加约束
这是一个类型良好的函数吗?
def triple(string: Union[str, bytes]) -> Union[str, bytes]:
return string * 3
并非如此:如果你传入一个字符串,你得到的永远是一个字符串,字节数也一样。这会给你带来一些麻烦,因为你知道什么时候会得到一个str
,什么时候会得到一个bytes
反义词。
“如果你传入str
,你就会得到str
。如果你传入bytes
,你就会得到bytes
”——听起来像是类型变量的工作。
这很合理——并非所有类型都支持乘法。我们可以限制我们的类型变量只能接受str
“或” bytes
(当然,也包括它们的子类)。
AnyString = TypeVar("AnyString", str, bytes)
def triple(string: AnyString) -> AnyString:
return string * 3
unicode_scream = triple("A") + "!"
bytes_scream = triple(b"A") + b"!"
使用类型变量作为参数
您还可以使用类型变量作为泛型类型的参数,例如list
或Iterable
。
def remove_falsey_from_list(items: list[T]) -> list[T]:
return [item for item in items if item]
def remove_falsey(items: Iterable[T]) -> Iterator[T]:
for item in items:
if item:
yield item
然而,这很快就会变得棘手。我将在下一篇文章中深入探讨这个问题。