如何约束 Union 以使输入和输出类型匹配?

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

我有一个测试用例.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?

python generics python-typing mypy
1个回答
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
中的
my_func
语句即可。

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