表示mypy中进行函数类型检查

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

我有一个工厂函数,它接受多个

Optional
参数,并根据哪些参数是
None
和哪些不是来创建对象。我还有一个函数检查其所有参数是否都是
None
,还有一个函数检查其所有参数是否不是
None
。我用这些来做检查。这是 MWE:

from typing import Optional

class Arg1:
    def __init__(self, arg1: float):
        pass

class Arg1And2:
    def __init__(self, arg1: float, arg2: float):
        pass

class Arg2And3:
    def __init__(self, arg2: float, arg3: float):
        pass

def _all_none(*args) -> bool:
    return all(a is None for a in args)

def _none_none(*args) -> bool:
    return all(a is not None for a in args)

def dispatch_factory(
    arg1: Optional[float] = None,
    arg2: Optional[float] = None,
    arg3: Optional[float] = None
):
    if _all_none(arg2, arg3) and _none_none(arg1):
        return Arg1(arg1)  # <-- This throws a mypy error
    elif _all_none(arg3) and _none_none(arg1, arg2):
        return Arg1And2(arg1, arg2)  # <-- And this
    elif _all_none(arg1) and _none_none(arg2, arg3):
        return Arg2And3(arg2, arg3)  # <-- And this
    else:
        raise ValueError("Combination of arguments invalid")

当然,我们知道我的

Arg1
构造是有效的,因为我们刚刚检查了
arg1
不是
None
,因此必须是
float
。我们对
Arg1And2
Arg2And3
知道相同的事情。我理解为什么 mypy 这样做 - 它实际上无法知道
arg1
不是
None
,因为
_none_none
中可能发生各种愚蠢的事情 - 但我有什么办法告诉 mypy
_none_none
保证其参数的类型,而无需在
cast(float, arg1)
函数中显式添加
dispatch_factory

(另外,如果有人有一个令人信服的替代方法来避免这个问题或更干净,我很高兴听到它。)

python python-typing mypy
1个回答
2
投票

验证分支中的类型称为类型防护。目前这是不可能的,但在PEP 647 -- 用户定义的类型防护中提出。

from typing import TypeGuard, Optional, TypeVar

T = TypeVar('T')

def all_none(args: list[Optional[T]]) -> TypeGuard[List[None]]:
    return all(a is None for a in args)

由于代码必须显式地将每个参数传递给守卫,因此展开检查的工作量大致相同 - 这适用于当前类型检查。

def dispatch_factory(
    arg1: Optional[float] = None,
    arg2: Optional[float] = None,
    arg3: Optional[float] = None
):
    if arg1 is not None and arg2 is None and arg3 is None:
        return Arg1(arg1)
    elif arg1 is not None and arg2 is not None and arg3 is None:
        return Arg1And2(arg1, arg2)
    elif arg1 is None and arg2 is not None and arg3 is not None:
        return Arg2And3(arg2, arg3)
    else:
        raise ValueError("Combination of arguments invalid")

通常,类型保护可以重新表述为“返回”值(如果它们属于正确的类型)或没有其他类型。这允许在 if 语句/表达式中使用防护(仅在返回任何内容时才继续)以及赋值表达式(以存储验证值)。

def _all_none(*args: Optional[T]) -> List[None]:
    if all(a is None for a in args):  # MyPy does not understand this to narrow `args`, so we need the next ignore
        return list(args)  # type: ignore
    return []

def _none_none(*args: Optional[T]) -> List[T]:
    if all(a is not None for a in args):
        return list(args)  # type: ignore
    return []

def dispatch_factory(
    arg1: Optional[float] = None,
    arg2: Optional[float] = None,
    arg3: Optional[float] = None
):
    if _all_none(arg2, arg3) and (args :=_none_none(arg1)):
        return Arg1(*args)
    elif _all_none(arg3) and (args := _none_none(arg1, arg2)):
        return Arg1And2(*args)
    elif _all_none(arg1) and (args := _none_none(arg1, arg2)):
        return Arg2And3(*args)
    else:
        raise ValueError("Combination of arguments invalid")

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