【问题标题】:Generic type interplay with Union type泛型类型与联合类型的相互作用
【发布时间】:2021-11-11 12:46:31
【问题描述】:

我在 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 在其所有参数中的行为都是协变的。实际上,如上所述,Union[t1, t2, ...]Union[u1, u2, ...] 的子类型,如果t1u1 的子类型等等。

但我对此有异议,因为我知道∀w ∈ Wrapper[Union[str, int]],w ∈ Wrapper[str] 或w ∈ Wrapper[int],因此,Wrapper[Union[str, int]] <:>Union[Wrapper[str], Wrapper[int]],无论如何。我想让mypy 承认同样的事实,但我不知道如何。

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

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 相关的行为是否有名称?如果我们将 Wrapper 和 Unions 视为类型上的函数,我们可以说它们 commute with each other,但不确定在类型理论或 Python 的上下文中正确的术语是什么。

【问题讨论】:

    标签: python generics type-hinting mypy python-typing


    【解决方案1】:

    我发现 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
    

    感觉有点矫枉过正,但这可能是唯一的办法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-07
      • 1970-01-01
      • 1970-01-01
      • 2023-02-10
      • 2016-11-13
      相关资源
      最近更新 更多