我试图为以下函数提供完美的函数签名(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
下会传递最优雅的解决方案是什么?
答案 0 :(得分:1)
所以,不幸的是,Python / PEP 484中的数字系统目前有点混乱。
我们在技术上有一个"numeric tower",它应该代表一组ABC,它们应该服从Python中所有“类似数字”的实体。
此外,Python中的许多内置类型(例如int
,float
,complex
和timedelta
)都不会从这些ABCs中继承typeshed - 这意味着这些ABC基本上不可用(除非您定义明确从这些ABC继承的自定义类型)。
为了解决这个问题,类型中的numbers module is largely dynamically typed - 我在大约一年前修复了数字模块,我的回忆是当时的mypy不够强大到准确地输入数字塔。
这种情况今天可能已经解决,但这或多或少都没有实际意义,因为mypy最近实施了对协议的实验性支持(例如结构化打字)!事实证明,这正是我们解决您的问题所需要的,并最终修复数字塔(一旦将协议添加到PEP 484和打字模块)。
目前,您需要做的是:
typing_extensions
模块(python3 -m pip install typing_extensions
)python3 -m pip install -U git+git://github.com/python/mypy.git
)然后,我们可以为“支持添加或分割”类型定义协议,如下所示:
from datetime import timedelta
from typing import TypeVar, Iterable
from typing_extensions import Protocol
T = TypeVar('T')
S = TypeVar('S', covariant=True)
class SupportsAddAndDivide(Protocol[S]):
def __add__(self: T, other: T) -> T: ...
def __truediv__(self, other: int) -> S: ...
def avg(xs: Iterable[SupportsAddAndDivide[S]]) -> S:
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
reveal_type(avg([1, 2, 3]))
reveal_type(avg([3.24, 4.22, 5.33]))
reveal_type(avg([3 + 2j, 3j]))
reveal_type(avg([timedelta(1), timedelta(2), timedelta(3)]))
使用mypy运行此命令会根据需要生成以下输出:
test.py:27: error: Revealed type is 'builtins.float*'
test.py:28: error: Revealed type is 'builtins.float*'
test.py:29: error: Revealed type is 'builtins.complex*'
test.py:30: error: Revealed type is 'datetime.timedelta*'