类型检查其值与另一个属性的值相关的可选属性

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

我有一个计算

Result
的函数,这个计算可以是
success
full 也可以不是。如果成功,也会返回一些总结计算结果的
data
。如果不成功,该数据将为
None
。现在的问题是,即使我验证了计算的状态 (
success
),类型检查器 (
mypy
) 也无法推断
success
data
之间的耦合。这可以通过以下代码进行总结:

from dataclasses import dataclass
from typing import Optional


@dataclass
class Result:
    success: bool
    data: Optional[int]  # This is not None if `success` is True.


def compute(inputs: str) -> Result:
    if inputs.startswith('!'):  # Oops, some condition that prevents the computation.
        return Result(success=False, data=None)
    return Result(success=True, data=len(inputs))


def check(inputs: str) -> bool:
    return (result := compute(inputs)).success and result.data > 2


assert check('123')
assert not check('12')
assert not check('!123')

针对此代码运行

mypy
会出现以下错误:

test.py:18: error: Unsupported operand types for < ("int" and "None")  [operator]
test.py:18: note: Left operand is of type "Optional[int]"

我考虑了以下解决方案,但我对其中任何一个都不满意。所以我想知道是否有更好的方法来解决这个问题。

typing.cast

函数

check
可以修改为使用
cast(int, result.data)
来强制
success
data
之间的逻辑关系。然而,不得不求助于
cast
感觉像是代码有问题的迹象(至少在这种情况下)。另外,每次在代码中使用此关系时,我都必须使用
cast
。最好在一处解决。

检查
result.data is not None

在上面的例子中,

success
data
之间的关系非常简单:
success == data is not None
。因此,我可以完全删除属性
success
,而是检查
result.data is not None

def check(inputs: str) -> bool:
    return (result := compute(inputs)).data is not None and result.data > 2

虽然这有效,但实际用例更加复杂,并且存在各种数据字段,例如

data_x
data_y
data_z
。在这种情况下,
success == all(d is not None for d in [data_x, data_y, data_z])
。使用它作为检查过于冗长,因此我将其重构为
property
类的
Result
。对于上面的例子,这将是:

@dataclass
class Result:
    data: Optional[int]

    @property
    def success(self) -> bool:
        return self.data is not None

但是,当

is not None
检查移入属性时,当
mypy
result.data
时,
None
无法再推断
result.success
确实不是
True

python mypy python-typing typechecking
1个回答
0
投票

您最初的问题与另一个问题非常接近,但总体问题不仅仅是这个,所以我会回答。

这是几个选项中的一个。

在一般情况下您有

success == all(d is not None for d in [data_x, data_y, data_z])

 的事实表明,您真正想要的是一个简单的 
Result
 类型,并组合它们。在 Haskell/Rust 等中有一个非常完善的模式,尽管它通常被称为 
Maybe
Option
 。在 Python 中,这看起来像

@dataclass class Success[T]: data: T class Fail: pass Result[T] = Success[T] | Fail
你会像这样使用它

def compute(inputs: str) -> Result[int]: if inputs.startswith('!'): # Oops, some condition that prevents the computation. return Fail() return Success(len(inputs))
现在我不清楚

check

在这里扮演什么角色,因为这取决于您的需求。最简单的方法是

def check(inputs: str) -> bool: match compute(inputs): case Success(x): return result.data > 2 case Fail(): return False
但是你可能会发现类似的功能

def is_success[T](r: Result[T]) -> bool: return isinstance(r, Success) def map[T, U](result: Result[T]) -> Result[U]: match result: case Success(x): return x case Fail: return Fail()
更普遍有用,在这种情况下你可能会这样做

def check(inputs: str) -> bool: return is_success(map(compute(inputs), lambda data: result.data > 2))
这不太漂亮,部分原因是我真的不知道 

check

 的用途。

当您有多个值时,您可以使用

组合器来组合多个函数,例如

def map2[T, U, V](r0: Result[T], r1: Result[U], f: Callable[[T, U], V]) -> Result[V]: match (r0, r1): case (Success(x0), Success(x1)): return f(x0, x1) case _: return Fail() @dataclass class TwoThings: data0: int data1: int hopefully_two_things: Result[TwoThings] = map2(compute("foo"), compute("bar"), TwoThings)
    
© www.soinside.com 2019 - 2024. All rights reserved.