问题描述

假设下面的测试

class Foo:

    def __init__(self):
        self.value: int | None = None

    def set_value(self, value: int | None):
        self.value = value


def test_foo():

    foo = Foo()
    assert foo.value is None
    foo.set_value(1)
    assert isinstance(foo.value, int)
    assert foo.value == 1 # unreachable

测试:

  • 首先,判断foo.value是否等于某个值
  • 然后,使用方法设置值.
  • 然后判断foo.value是否改变了.

当使用mypy版本1.9.0运行测试(最新版本在 compose 本文时),并将warn_unreachable设置为True时,会得到:

(venv) niko@niko-ubuntu-home:~/code/myproj$ python -m mypy tests/test_foo.py 
tests/test_foo.py:16: error: Statement is unreachable  [unreachable]
Found 1 error in 1 file (checked 1 source file)

我所发现的

from safe_assert import safe_assert

def test_foo():

    foo = Foo()
    safe_assert(foo.value is None)
    foo.set_value(1)
    safe_assert(isinstance(foo.value, int))
    assert foo.value == 1

问题仍然存在(safe—assert 0.4.0). 这一次,mypy和VS Code Pylance都认为上面的foo.set_value(1)两行是无法达到的.

问题

我怎么能对mypy说,foo.value已经变成了int,它应该继续判断assert isinstance(foo.value, int)线以下的所有东西?

推荐答案

您可以使用TypeGuard特殊表单(PEP 647)显式控制类型缩小.虽然通常你会使用TypeGuard来进一步缩小一个类型比已经推断的更大,但你可以使用它来"缩小"到你 Select 的任何类型,即使它与类型判断器已经推断的不同或更宽.

在这种情况下,我们将编写一个函数_value_is_set,它用返回类型TypeGuard[int]进行注释,这样类型判断器mypy将在调用该函数时推断出值"type guarded"的类型int(例如,assertif表达).

from typing import TypeGuard, Any

# ...

def _value_is_set(value: Any) -> TypeGuard[int]:
    if isinstance(value, int):
        return True
    return False

def test_foo():
    foo = Foo()
    assert foo.value is None
    foo.set_value(1)
    assert _value_is_set(foo.value)
    # the next line is redundant now, but can be kept without issue
    assert isinstnace(foo.value, int) 
    assert foo.value == 1 # now reachable, according to mypy

通常,mypy应该以类似的方式对待assert isinstance(...)if isinstance(...).但不管是什么原因,在这个案子里没有.使用TypeGuard,我们可以粗略地处理类型判断器来做正确的事情.

应用此更改后,mypy will not think this code is unreachable.

Python相关问答推荐

提取两行之间的标题的常规表达

重新匹配{ }中包含的文本,其中文本可能包含{{var}

rame中不兼容的d类型

如何在Python中并行化以下搜索?

我们可以为Flask模型中的id字段主键设置默认uuid吗

为什么以这种方式调用pd.ExcelWriter会创建无效的文件格式或扩展名?

如何使用scipy的curve_fit与约束,其中拟合的曲线总是在观测值之下?

有没有一种ONE—LINER的方法给一个框架的每一行一个由整数和字符串组成的唯一id?

isinstance()在使用dill.dump和dill.load后,对列表中包含的对象失败

为什么np. exp(1000)给出溢出警告,而np. exp(—100000)没有给出下溢警告?

try 检索blob名称列表时出现错误填充错误""

为什么if2/if3会提供两种不同的输出?

Python Tkinter为特定样式调整所有ttkbootstrap或ttk Button填充的大小,适用于所有主题

下三角形掩码与seaborn clustermap bug

有没有办法在不先将文件写入内存的情况下做到这一点?

多个矩阵的张量积

如何为需要初始化的具体类实现依赖反转和接口分离?

利用广播使减法更有效率

如何在不遇到IndexError的情况下将基数10的整数转换为基数80?

如何在Python中画一个只能在对角线内裁剪的圆?