Generics
时,python给出了以下示例:
from collections.abc import Sequence
from typing import TypeVar
T = TypeVar('T') # Declare type variable
def first(l: Sequence[T]) -> T: # Generic function
return l[0]
有人可以解释一下
TypeVar
在这种情况下的作用吗?例如,如果它可以是任何东西,为什么不直接给它值Any
?如果它受到约束,为什么不给它一个 Union
值呢?换句话说,使用TypeVar(...)
有什么用处?
我想在回顾上面的内容时,它是在某种子元素的元素时使用的?例如,它可以是:
[{set1}, {set2}, {set3}, ...]
这种类型是
Sequence[set] -> set
但是类似这样的事情:
[1, 2, 3, ...]
将具有类型
Sequence[int] -> int
。除了这个“item-in-iterable”还有其他用法吗?
在此上下文中
TypeVar
的目的是说该函数返回与参数类型相关的特定类型。
例如,如果您这样做:
a = first([1, 2, 3]) + "foo"
你会得到一个错误,因为在这个表达式中
T
绑定到类型 int
,所以你会得到一个关于添加 int
和 str
的错误。
如果您按照您的描述用
first
类型注释 Any
,这不会产生 mypy 错误(因此您会在运行时得到 TypeError
),因为 first
的返回值总是简单的是Any
。
有关如何使用类型变量的更多示例,请参阅有关泛型的 mypy 文档:https://mypy.readthedocs.io/en/stable/generics.html
最好的例子是 pydantic。
假设我有一个实现 pydantic 的函数,并且我希望该函数能够处理从 firestore 作为字典检索的记录的 pydantic 类型。该代码可能看起来像这样:
class MyModel(BaseModel):
...
class MyClass:
def get_records(...) -> T.Generator[MyModel, None, None]:
for record in self.client.collection("MyModel").where(...).stream(...):
body = record.to_dict()
if body:
yield MyModel.model_validate(body)
现在这一切都很好,但是如果我有多个模型,那么我必须为每个模型定义一个函数,对吧?真烦人。
好吧,如果我使用 Union 会怎样。
class MyModel(BaseModel):
...
class MyModel2(BaseModel):
...
my_union_typeT: T.TypeAlias = T.Union[MyModel, MyModel2]
class MyClass:
def get_records(
my_union_type: T.Type[my_union_typeT],
collection_name: str
) -> T.Generator[my_union_typeT, None, None]:
for record in self.client.collection(collection_name).where(...).stream(...):
body = record.to_dict()
if body:
if isinstance(my_union_typeT, MyModel):
yield MyModel.model_validate(body)
elif isinstance(my_union_typeT, MyModel2):
yield MyModel2.model_validate(body)
如您所见,我们现在可以使用我们的新类型,但它有点混乱,不是吗?我们正在调用相同的方法
model_validate
,但唯一的问题是,我们不能以动态方式引用类型,只能像这样说“未知的基础模型”。
而且,更重要的是,对于我们希望它处理的每种类型,我们必须重复相同的逻辑......
Step in
TypeVar
... TypeVar
允许我们为 Type
指定变量,而不是将类型定义为类型。
如果这没有意义,请这样想:
# this is a type stored in a variable
my_model_as_a_type = MyModel
# this is an instance stored in a variable
mymodel_as_an_instance = MyModel()
如您所见,类型实际上是定义该对象看起来是什么样的事物,但它不能用作作为该对象,因为它不是它的实例。
实例是在内存中初始化的东西,具有所有函数以及其类型的实现已定义的任何其他内容。
那么,转向 typevar...那么我们如何改进我们的代码呢?
好吧,通过一些简单的更改,我们可以使我们的函数接受任何基本模型,但没有明确表示它必须 be
BaseModel
本身,例如可以是子函数或其他函数,并且函数的调用者可以看到返回给他们的类型...
class MyModel(BaseModel):
...
class MyModel2(BaseModel):
...
model_typeT = T.TypeVar("model_typeT", bound=BaseModel)
class MyClass:
def get_records(
model_type: T.Type[model_typeT],
collection_path: str
) -> T.Generator[model_typeT , None, None]:
adapter = TypeAdapter(model_type)
for record in self.client.collection(collection_path).where(...).stream(...):
body = record.to_dict()
if body:
yield adapter.validate_python(body)
现在,当您将模型传递给此函数时,您返回的对象将是 firestore 集合中的 实例,即您所提供的类型。类型检查器很高兴,您的同事也很高兴,因为现在他们知道返回的类型是什么,而不是“可能”是一个联合,您可以拥有定义为only对特定类型的特定集合进行操作的函数。 ..