【问题标题】:How to constrain a Union so that input and output types match?如何约束 Union 以使输入和输出类型匹配?
【发布时间】:2021-11-24 17:56:54
【问题描述】:

我有一个 testcase.py:

import pathlib
import typing as tp


# Not under my control

PathType = tp.Union[str, pathlib.Path]
def foreign(filename: PathType) -> PathType:
    return filename


# Under my control

T = tp.TypeVar('T', str, pathlib.Path)

def my_func(filename: T) -> T:
    return foreign(filename)


if __name__ == '__main__':
    path1: str = '/abc/efg/string.py'
    san_path1: str = my_func(path1)
    print(san_path1, type(san_path1))

    path2: pathlib.Path = pathlib.Path('/abc/efg/pathlib.py')
    san_path2: pathlib.Path = my_func(path2)
    print(san_path2, type(san_path2))

此文件中有两个部分。 在“不受我控制”部分中,我正在模拟不受我控制的模块的功能,但此功能的定义如下所示。

在“在我的控制下”部分,我试图强制执行,如果我用 str 调用 my_func 来取回 str,或者如果我用 pathlib.Path 调用 my_func 来取回 pathlib.Path,但要防止出现以下情况我将用 str 调用该函数并取回 pythlib.Path ,反之亦然。

代码运行良好。如果我运行它,输出是:

$ python testcase.py 
/abc/efg/string.py <class 'str'>
/abc/efg/pathlib.py <class 'pathlib.PosixPath'>

但是 mypy 抱怨:

$ mypy testcase.py 
testcase.py:17: error: Incompatible return value type (got "Union[str, Path]", expected "str")
testcase.py:17: error: Incompatible return value type (got "Union[str, Path]", expected "Path")
Found 2 errors in 1 file (checked 1 source file)

第 17 行是return foreign(filename)。 如何满足mypy?

【问题讨论】:

  • 你能保证foreign 一定会为strPathPath 返回str 吗?这是作为 API 的一部分记录的,还是只是当前的行为?你能确定他们不会在不告诉你的情况下改变实施吗?他们可能正在使用Union,因为他们不想限制自己并打算稍后更改功能
  • 对糟糕的标题投了反对票
  • 我不能保证任何关于外国的,但我想确保 my_func 能像我解释的那样工作。如果他们改变外部功能,我的测试将失败,这没关系。请建议更好的标题。
  • 如果你不能保证外部函数的实现,mypy 错误是正确的,你不能保证my_func 你想要的(除非你抛出无效类型的异常)
  • 很高兴建议一个标题,但首先我需要了解您的问题

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


【解决方案1】:

foreign 不保证它会为str 输入返回一个str,对于Path 也是如此。它只保证类型中的内容(除非文档另有说明):无论输入如何,它都会返回任一类型。由于它不在您的控制范围内,因此作者可以更改实现,例如它总是返回一个Path,从而破坏你的代码。

如果您不能确定 foreign 的工作原理,您可以根据需要进行转换

def my_func(filename: T) -> T:
    res = foreign(filename)
    
    return str(res) if isinstance(filename, str) else pathlib.Path(res)

或者,如果您有检查 foreign 的测试返回与给定相同的类型,您可以断言 my_func 中的类型

def my_func(filename: T) -> T:
    res = foreign(filename)

    if isinstance(filename, str):
        assert instance(res, str)
        return res
    else:
        assert instance(res, pathlib.Path)
        return res

甚至更好的失败退出

def my_func(filename: T) -> T:
    res = foreign(filename)

    if isinstance(filename, str) and isinstance(res, str):
        return res
    elif isinstance(filename, pathlib.Path) and isinstance(res, pathlib.Path):
        return res
        
    sys.exit("Fatal error")  # we exit if our code's broken

或者,如果您不希望这些检查的运行时成本,并且您乐于依靠运行时测试来验证 foreign,只需 # type: ignore return 中的 return 语句@ 语句。

【讨论】:

  • 我有一个(py)测试。我不在乎外国人做什么。我只是想从我这边和我对 my_func 的使用确保每当我用 str 调用它以获取 str 并且如果我用 Path 调用它以获取 Path 回来,我希望 mypy 作为其中的一部分检查该保证我的测试套件。
  • @user3225309 如果您有测试,我添加了一个选项。请在您的问题中提及这些事情。它使人们更容易回答
  • 我不明白是什么意思:“如果你有测试,我已经添加了一个选项”。
  • @user3225309 最后两个代码 sn-ps 用于如果您确信(例如通过 pytest 测试)foreign 返回的类型相同
猜你喜欢
  • 1970-01-01
  • 2021-10-09
  • 1970-01-01
  • 2020-06-18
  • 1970-01-01
  • 2022-06-21
  • 2023-04-09
  • 1970-01-01
  • 2021-04-08
相关资源
最近更新 更多