微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Python 中嵌套的“Union”类型的模式匹配

如何解决Python 中嵌套的“Union”类型的模式匹配

构建一个 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 类型,但错误消息表明它不是案例...

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

解决方法

您的用例是使用 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

它是可扩展的、可读的,并且不会在 linters/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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。