泛型类型与联合类型的相互作用

2024-04-25 13:42:22 发布

您现在位置:Python中文网/ 问答频道 /正文

我在python中使用了一个Generic类,它的实例很少,只是围绕其他类型的实例进行包装:

import random
from typing import Generic
from typing import TypeVar
from typing import Union

T = TypeVar("T", covariant=True)


class Wrapper(Generic[T]):
    def __init__(self, value: T) -> None:
        self._value = value

    @property
    def unwrap(self) -> T:
        return self._value


def test_union_wrapper() -> None:
    def wrapper_union() -> Wrapper[Union[str, int]]:
        if random.random() >= 0.5:
            return Wrapper("foo")
        else:
            return Wrapper(42)

    # mypy will give an error for this line
    w_u: Union[Wrapper[str], Wrapper[int]] = wrapper_union()

在上述代码上运行mypy将导致:

error: Incompatible types in assignment (expression has type "Wrapper[Union[str, int]]", variable has type "Union[Wrapper[str], Wrapper[int]]")

这似乎是合理的,因为

Wrapper[Union[str, int]]≮: Wrapper[str],以及 Wrapper[Union[str, int]]≮: Wrapper[int]

而且,正如在PEP483中可以看到的那样:

Union behaves covariantly in all its arguments. Indeed, as discussed above, Union[t1, t2, ...] is a subtype of Union[u1, u2, ...], if t1 is a subtype of u1, etc.

但我不同意,因为我知道这一点∀W∈ Wrapper[Union[str, int]],w∈ Wrapper[str]或w∈ Wrapper[int],因此,{}<;:Union[Wrapper[str], Wrapper[int]],不管怎样。我想让mypy认识到同样的事实,但我不知道如何认识

甚至有一个使用标准库进行识别的例子。如果我在上面的代码中用另一个协变变量Generic替换Wrapper,我们得到:

def test_union_type() -> None:
    def type_union() -> Type[Union[str, int]]:
        if random.random() >= 0.5:
            return str
        else:
            return int

    # mypy has no problem with this
    w_u: Union[Type[str], Type[int]] = type_union()

这里,mypy识别函数返回类型Type[Union[str, int]]等价于变量类型Union[Type[str], Type[int]]

因此,我有两个问题:

  1. 如何告诉类型检查器Wrapper的行为与TypeUnion的行为相同
  2. 关于Union的这种行为有名称吗?如果我们将包装器和联合视为类型上的函数,我们可以说它们commute with each other,但不确定在类型理论或Python的上下文中正确的术语是什么

Tags: importself类型returndeftyperandomwrapper
1条回答
网友
1楼 · 发布于 2024-04-25 13:42:22

我发现了一个对Q1不满意的解决方案,使用mypy插件和以下类型分析器挂钩:

def commute_with_union_hook(ctx: AnalyzeTypeContext) -> Type:
    sym = ctx.api.lookup_qualified(ctx.type.name, ctx.type)
    node = sym.node
    if not ctx.type.args and len(ctx.type.args) == 1:
        return ctx.api.analyze_type_with_type_info(node, ctx.type.args, ctx.type)

    t: Type = ctx.type.args[0]
    t = ctx.api.anal_type(t)

    if not isinstance(t, UnionType):
        wrapper = ctx.api.analyze_type_with_type_info(node, ctx.type.args, ctx.type)
        return wrapper
    else:
        union_wrapper = UnionType.make_union(
            items=[Instance(typ=node, args=[item]) for item in t.items]
        )
        return union_wrapper

感觉太过分了,但这可能是唯一的办法

相关问题 更多 >