如何解决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
只能是 float
或 int
类型,但错误消息表明它不是案例...
如何在这种嵌套类型的“部分”上实现某种模式匹配?
解决方法
您的用例是使用 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 举报,一经查实,本站将立刻删除。