我正在使用mypy,遇到了意想不到的行为.Mypy错误地推断出预期类型的类型

from typing import Generic, TypeVar, Callable, reveal_type

S1 = TypeVar('S1')
F1 = TypeVar('F1')
I = TypeVar('I')


class Node(Generic[I, S1, F1]):
    def __init__(self, callback: Callable[[I], S1 | F1]):
        self.callback: Callable[[I], S1 | F1] = callback


class Succ1:
    ...


class Fail1:
    ...


def func1(_: str) -> Succ1 | Fail1:
    return Succ1()


n1 = Node(func1)
res1 = n1.callback("str")
reveal_type(n1)
% mypy isolated_example2.py
isolated_example2.py:25: error: Need type annotation for "n1"  [var-annotated]
isolated_example2.py:25: error: Argument 1 to "Node" has incompatible type "Callable[[str], Succ1 | Fail1]"; expected "Callable[[str], Never]"  [arg-type]
isolated_example2.py:27: note: Revealed type is "isolated_example2.Node[builtins.str, Any, Any]"
Found 2 errors in 1 file (checked 1 source file)

我不期望类型Callable[[stat],Never],并且认为没有理由这样认为.可能是什么问题?

mypy==1.9.0
Python 3.12.3

这是一个更大问题的一部分,但我试图将其分成独立的块,以更好地理解流程

UPD:看起来像Unioning TypVar中的一个问题.这是重现错误的最小示例

from typing import TypeVar

S1 = TypeVar('S1')
F1 = TypeVar('F1')


def func(arg: S1 | F1):
    return arg


func(None)

推荐答案

Union显然是交换的,对吧?您不能随意将unions 分成两个ordered部分-如果您的班级中有def foo(self) -> S1部分怎么办?

class Node(Generic[I, S1, F1]):
    def __init__(self, callback: Callable[[I], S1 | F1]):
        self.callback: Callable[[I], S1 | F1] = callback
    
    def foo(self) -> S1:
        # Huh? Succ1? Fail1? Succ1 | Fail1? object? Something else?
        raise NotImplementedError

S1应该在这里解析为Succ1还是Fail1?为什么?

因此,您需要某种方法来告诉类型判断器如何正确拆分变量.您可以为此使用绑定类型变量:

from typing import Generic, TypeVar, Callable, Literal, Protocol, reveal_type

class SuccBase(Protocol):
    success: Literal[True]
class FailBase(Protocol):
    success: Literal[False]

S1 = TypeVar('S1', bound=SuccBase)
F1 = TypeVar('F1', bound=FailBase)
I = TypeVar('I')

class Node(Generic[I, S1, F1]):
    def __init__(self, callback: Callable[[I], S1 | F1]):
        self.callback: Callable[[I], S1 | F1] = callback


# Pyright correctly accepts without protocol inheritance
# For mypy need to inherit protocol explicitly (will report a bug tomorrow?)
class Succ1(SuccBase):
    success: Literal[True] = True
class Fail1(FailBase):
    success: Literal[False] = False
class Fail2(FailBase):
    success: Literal[False] = False


def func1(_: str) -> Succ1 | Fail1 | Fail2:
    return Succ1()


n1 = Node(func1)
res1 = n1.callback("str")
reveal_type(n1)

mypy playgroundpyright playground

我正在使用上面的协议中的"区分联合"类解决方案,但您也可以要求显式继承(ABC或只是一个没有属性的普通FailBase类),或者使用在您的上下文中有意义的不同属性,或者调整其他东西-关键点是您需要清楚地指出什么应该进入S1以及什么-进入F1.

如果您完全控制API设计,请考虑不这样做,而是返回适当的类似Result的类型(例如https://github.com/rustedpy/result,或者推出您自己的类型-这很微不足道).例如,这将允许使永不失败的函数(-> Result[Succ1, Never])更加明确.或者只是完全摆脱Fail1,不要返回异常,也不要引发它们- Python是围绕异常构建的,除非处于紧密循环中,否则性能损失相当低.

侧记:你在那里看到Never只是因为推论是不可能的.这是一个副作用;如果您明确注释n1,这种副作用就会消失.

Python相关问答推荐

实现的差异取决于计算出的表达是直接返回还是首先存储在变量中然后返回

从DataFrame.apply创建DataFrame

jit JAX函数中的迭代器

如何使用Jinja语法在HTML中重定向期间传递变量?

将DF中的名称与另一DF拆分并匹配并返回匹配的公司

输出中带有南的亚麻神经网络

NP.round解算数据后NP.unique

ODE集成中如何终止solve_ivp的无限运行

如何使用pytest来查看Python中是否存在class attribution属性?

pyscript中的压痕问题

如何从pandas的rame类继承并使用filepath实例化

Asyncio:如何从子进程中读取stdout?

Python—压缩叶 map html作为邮箱附件并通过sendgrid发送

循环浏览每个客户记录,以获取他们来自的第一个/最后一个渠道

如果有2个或3个,则从pandas列中删除空格

当我定义一个继承的类时,我可以避免使用`metaclass=`吗?

Django.core.exceptions.SynchronousOnlyOperation您不能从异步上下文中调用它-请使用线程或SYNC_TO_ASYNC

为什么在Python中00是一个有效的整数?

为什么我只用exec()函数运行了一次文件,而Python却运行了两次?

高效地计算数字数组中三行上三个点之间的Angular