了解TypeVar的用法

问题描述 投票:0回答:2

在谈到

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”还有其他用法吗?

python
2个回答
7
投票

在此上下文中

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


0
投票

最好的例子是 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对特定类型的特定集合进行操作的函数。 ..

© www.soinside.com 2019 - 2024. All rights reserved.