Python中嵌套的'Union'类型上的模式匹配

2024-06-01 00:09:59 发布

您现在位置:Python中文网/ 问答频道 /正文

在构建Python库时,我使用类型提示来保证某些数据表示的一致性。特别是,我以嵌套方式使用Union(总和类型)来表示数据可以采用的不同“风格”

到目前为止,我得到的结果与以下示例类似:

from typing import Union

MyNumberT = Union[float,int]
MyDataT = Union[str,MyNumber]

def my_data_to_string(datum: MyDataT) -> str:
    if isinstance(datum, float):
        return _my_number_to_string(datum)
    elif isinstance(datum, int):
        return _my_number_to_string(datum)
    elif isinstance(datum, str):
        return datum
    # assert_never omitted for simplicity

def _my_number_to_string(number: MyNumberT) -> str:
    return "%s" % number

哪种类型使用mypy检查良好

现在,我的实际代码有点复杂,我需要对MyNumberT类型的变量执行一些常见的操作。 在本例中,通过调整import并替换my_data_to_string来强调这一点,如下所示:

from typing import get_args, Union

[...]

def my_data_to_string(datum: MyDataT) -> str:
    if isinstance(datum, get_args(MyNumberT)):
        return _my_number_to_string(datum)
    elif isinstance(datum, str):
        return datum
    # assert_never omitted for simplicity

[...]

在其上mypy的类型检查失败: Argument 1 to "_my_number_to_string" has incompatible type "Union[str, Union[float, int]]"; expected "Union[float, int]"

我希望mypy能够“意识到”在第一个分支中,datum只能是floatint类型,但错误消息表明情况并非如此

如何在这种嵌套类型的“部分”上实现一些模式匹配


Tags: toimport类型numberstringreturnmyfloat
2条回答

您的用例是使用functools提供的名为singledispatch的实用程序的一个很好的例子。它允许您根据输入类型将多个功能定义为单个功能

from functools import singledispatch

# This class defines the function with
# a base case if the input type doesn't match
@singledispatch
def my_data_to_string(datum) -> str:
    raise TypeError(f"unsupported format: {type(datum)}")

# Registering for type str using type hint
@my_data_to_string.register
def _(datum: str):
    return datum

# Registering for multiple 
# types using decorator
@my_data_to_string.register(float)
@my_data_to_string.register(int)
def _(datum):
    return "<%s>" % datum


print(my_data_to_string("a"))    # a
print(my_data_to_string(1))      # <1>
print(my_data_to_string(1.5))    # <1.5>
print(my_data_to_string([1, 2])) # TypeError

它是可扩展的、可读的,并且不会在linter/formatters中生成错误Docs link

从Python 3.10开始,unions are valid for isinstance checks

def my_data_to_string(datum: MyDataT) -> str:
    if isinstance(datum, MyNumberT):
        return _my_number_to_string(datum)
    elif isinstance(datum, str):
        return datum
    # assert_never omitted for simplicity

只要足以排除工会的一个组成部分,无需要求即可撤销检查工作:

def my_data_to_string(datum: MyDataT) -> str:
    if isinstance(datum, str):  # handle explicit type first
        return datum
    else:  # catch-all for remaining types
        return _my_number_to_string(datum)
    # rely on type checker for safety!

请注意,这使用了else而不是elif子句–依赖类型检查器拒绝类型错误的参数


对于更复杂的类型,可以构建类型保护:

def guard_mnt(arg: MyDataT) -> Union[Literal[False], Tuple[MyNumberT]]:
    return (arg,) if isinstance(arg, get_args(MyNumberT)) else False  # type: ignore

这告诉类型检查器,它将返回所需的已包装类型,或者返回错误的内容。type: ignore是必需的,因为它使用相同的类型检查实现;该函数用作围绕不支持的运行时检查添加有效的静态类型检查

它可以通过赋值表达式和解包来使用:

def my_data_to_string(datum: MyDataT) -> str:
    if nums := guard_mnt(datum):  # only enter branch if guard is not False
        return _my_number_to_string(*datum)
    elif isinstance(datum, str):
        return datum
    # assert_never omitted for simplicity

相关问题 更多 >