【发布时间】:2017-09-23 21:07:08
【问题描述】:
我正在尝试为以下函数(Python 3.6,mypy 0.521)提出完美的函数签名:
def avg(xs):
it = iter(xs)
try:
s = next(it)
i = 1
except StopIteration:
raise ValueError("Cannot average empty sequence")
for x in it:
s += x
i += 1
return s / i
这段代码的好处是它可以与int、float、complex 的可迭代对象一起工作,并为datetime.timedelta 生成正确的结果。尝试添加签名时会出现问题。我尝试了以下方法:
def avg(xs: t.Iterable[t.Any]) -> t.Any: ...
但是现在,调用者需要转换结果。
def avg(xs: t.Iterable[T]) -> T: ...
这会失败,因为T 不支持加法或除法。
N = TypeVar("N", int, float, complex, datetime.timedelta)
def avg(xs: t.Iterable[N]) -> N: ...
因为int / int 是float 而失败;使用// 会为几乎所有其他事情提供错误的结果。也很烂,因为代码应该适用于其他类型,只要支持加法和除法。
N = TypeVar("N", float, complex, datetime.timedelta)
def avg(xs: t.Iterable[N]) -> N: ...
这几乎是完美的,但同样,如果有人后来决定向它扔四元数,mypy 会抱怨。
...然后我也尝试了 abc 和 typing.overload 的一些东西,但这让我无处可去。
mypy --strict 下最优雅的解决方案是什么?
【问题讨论】:
-
看起来 float/int 不对称意味着你不能真正为此创建一致的签名。它为整数和浮点数在数字意义上产生“正确的结果”,但
avg([list of ints])产生一个浮点数,而avg([list of floats])也产生一个浮点数。这意味着您的函数有时会返回与给定类型相同的类型,有时还会返回另一种类型,因此它没有根据其输入类型始终可定义的返回类型。 mypy 是否允许像“数字”这样的类型(如numbers.Number)? -
太疯狂了,
numbers.Number没有定义__add__或其他标准算术运算,所以我得到Unsupported left operand type for + ("Number")、Unsupported operand types for / ("Number" and "int")等。