此外:此问题的标题不太理想。我想做的事情可以通过计算类型来实现,也可以通过其他方式来实现。
我正在编写一些代码来验证,有时将动态类型的JSON数据转换为静态Python类型。以下是一些功能:
def from_str(x: Any) -> str:
assert isinstance(x, str)
return x
def from_int(x: Any) -> int:
assert isinstance(x, int)
return x
def from_list(f: Callable[[Any], T], x: Any) -> List[T]:
assert isinstance(x, list)
return [f(y) for y in x]
这些工作很棒。我还希望能够将它们组合以转换联合类型。理想的情况是这样:
union = from_union([from_str, from_int], json)
问题在于如何键入from_union
函数。我的第一种方法是:
def from_union(fs: Iterable[Callable[[Any], T]], x: Any) -> T:
for f in fs:
try:
return f(x)
except AssertionError:
pass
assert False
从技术上讲这是正确的。如果我们用Union[str,int]
代替T
,则上面的表达式是正确键入的,因为from_str
通过返回str
也会返回Union[str,int]
(类型str
是类型Union[str,int]
的值)。但是,mypy
不想执行此替换:
test/fixtures/python/quicktype.py:59: error: Argument 1 to "from_union" has incompatible type "List[Callable[[Any], object]]"; expected "Iterable[Callable[[Any], <nothing>]]"
似乎可以直接object
,而不是推断Union[str,int]
。
理想情况下,我想给from_union
的类型是
def from_union(fs: Iterable[Union[[Callable[[Any], S], Callable[[Any], T], ...]], x: Any) -> Union[S, T, ...]):
Python的输入不支持该功能。另一个选择是能够指定一个函数,该函数可以根据特定调用的实际返回类型来计算fs
的类型,也可以采用其他方法。这样有可能吗?无需借助cast
还能有其他选择吗?
答案 0 :(得分:1)
很遗憾,这不是在Python的类型系统中可以表达的东西。最好的可用解决方法(与Typeshed用于键入map
,filter
和zip
之类的内置方法的解决方法)是滥用重载,如下所示:
from typing import Iterable, Callable, Any, Union, TypeVar, overload, List
T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
# Note: the two underscores tell mypy that the argument is positional-only
# and that doing things like `from_union(blob, f1=from_str)` is not legal
@overload
def from_union(x: Any,
__f1: Callable[[Any], T1],
) -> T1: ...
@overload
def from_union(x: Any,
__f1: Callable[[Any], T1],
__f2: Callable[[Any], T2],
) -> Union[T1, T2]: ...
@overload
def from_union(x: Any,
__f1: Callable[[Any], T1],
__f2: Callable[[Any], T2],
__f3: Callable[[Any], T3],
) -> Union[T1, T2, T3]: ...
# The fallback: give up on the remaining callables
@overload
def from_union(x: Any,
__f1: Callable[[Any], T1],
__f2: Callable[[Any], T2],
__f3: Callable[[Any], T3],
*fs: Callable[[Any], Any]
) -> Union[T1, T2, T3, Any]: ...
def from_union(x: Any, *fs: Callable[[Any], Any]) -> Any:
for f in fs:
try:
return f(x)
except AssertionError:
pass
assert False
此函数的基本作用是硬代码,最多支持三个可调用对象,如果尝试传递更多,则放弃。当然,要支持接受更多可调用对象,请添加更多重载。
此新函数的API确实有少许变化:需要这样调用:
my_union = from_union(json_blob, from_str, from_int)
如果您希望使用一个与原始API更相似的API,并且首先使用函数,则需要将x
转换为仅关键字的参数(例如from_union(*fs: Callable[[Any], Any], *, x: Any) -> Any
)或存储在元组中起作用,如下所示:
@overload
def from_union(fs: Tuple[Callable[[Any], T1]], x: Any) -> T1: ...
@overload
def from_union(fs: Tuple[Callable[[Any], T1], Callable[[Any], T2]], x: Any) -> Union[T1, T2]: ...
# etc...
# The final fallback: have the tuple accept any number of callables
@overload
def from_union(fs: Tuple[Callable[[Any], Any], ...], x: Any) -> Any: ...
def from_union(fs: Tuple[Callable[[Any], Any], ...], x: Any) -> Any:
for f in fs:
try:
return f(x)
except AssertionError:
pass
assert False
在两种情况下,如果用户传入的参数过多,则“后备”将在输出中引入一些动态性。如果您不喜欢这样,只需删除最后的备用。