我有一个简单的左折功能,如下:
from typing import Iterable, Callable, Optional, TypeVar, overload
S = TypeVar("S")
T = TypeVar("T")
def fold_left(it: Iterable[S], f: Callable[[T, S], T], init: Optional[T] = None) -> T:
it = iter(it)
if init is None:
try:
acc = next(it)
except StopIteration:
raise ValueError("fold_left given empty iterable with no init")
else:
acc = init
for i in it:
acc = f(acc, i)
return acc
Mypy在检查该代码时抛出以下错误:
10: error: Incompatible types in assignment (expression has type "T", variable has type "S")
13: error: Incompatible types in assignment (expression has type "T", variable has type "S")
13: error: Argument 1 has incompatible type "S"; expected "T"
15: error: Incompatible return value type (got "S", expected "T")
[Mypy似乎不喜欢这样的事实,当init is None
时,类型S和T将相同。是否有某种方法可以使代码正确输入以便正确键入检查?
我尝试使用以下行进行重载,但没有任何效果:
@overload
def fold_left(it: Iterable[S], f: Callable[[T, S], T], init: T) -> T:
...
@overload
def fold_left(it: Iterable[S], f: Callable[[S, S], S]) -> S:
...
这个问题有两个部分。
首先,对函数的外部接口进行类型检查。这是与overload
相关的地方。
@overload
def fold_left(it: Iterable[S], f: Callable[[T, S], T], init: T) -> T:
...
@overload
def fold_left(it: Iterable[S], f: Callable[[S, S], S]) -> S:
...
这指定了函数的所有有效签名。
但是,这((直接)对我们函数的类型检查没有帮助。我们需要独立解决此问题。
def fold_left(it: Iterable[S], f: Callable[[T, S], T], init: Optional[T] = None) -> T:
# we can't change the type of it which is already defined to be an
# `Iterable`, so in order for `next` to type check we need a new
# variable of type `Iterator`
itor: Iterator[S] = iter(it)
# acc contains or return value it therefore has to be of type `T`
acc: T
if init is None:
try:
# mypy isn't smart enough to figure out that if `init` is
# `None`, then `S` is equal to `T`, we therefore need to tell it
# that this assignment is correct
acc = cast(T, next(itor))
except StopIteration:
raise ValueError("fold_left given empty iterable with no init")
else:
acc = init
for i in itor:
acc = f(acc, i)
return acc
overload
语句确实间接地帮助了我们,因为它们以某种方式限制了我们函数的公共接口,使我们可以推断我们在实现中使用的cast
是正确的。