Mypy:寻找一个平均函数的完美签名

2024-04-20 11:18:44 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图为以下函数(python3.6,mypy0.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

这段代码的好处是,它可以与intfloatcomplex的iterables生成正确的结果,也可以为datetime.timedelta生成正确的结果。尝试添加签名时会出现问题。我试过以下方法:

^{pr2}$

但现在,调用者需要转换结果。在

def avg(xs: t.Iterable[T]) -> T: ...

此操作失败,因为T不支持加法或除法。在

N = TypeVar("N", int, float, complex, datetime.timedelta)
def avg(xs: t.Iterable[N]) -> N: ...

失败的原因是int / intfloat;使用//对几乎所有其他内容都给出了错误的结果。也很糟糕,因为只要支持加法和除法,代码就可以用于其他类型。在

N = TypeVar("N", float, complex, datetime.timedelta)
def avg(xs: t.Iterable[N]) -> N: ...

这几乎是完美的,但是,如果后来有人决定用四元数来表示,mypy会抱怨的。在

…然后我也在尝试使用abc和{}的东西,但这没给我带来任何好处。在

mypy --strict下通过的最优雅的解决方案是什么?在


Tags: 函数代码datetimedefitfloatiterabletimedelta
1条回答
网友
1楼 · 发布于 2024-04-20 11:18:44

因此,不幸的是,Python/pep484中的数字系统目前有点混乱。在

从技术上讲,我们有一个"numeric tower"来表示一组abc,Python中所有“类似数字”的实体都应该遵守这些abc。在

在这种情况下,{cd3}基本上都是从这些自定义类型继承而来的。在

为了解决这个问题,我在大约一年前,在typeshed中的numbers module is largely dynamically typed曾尝试过修复数字模块,而我的记忆是当时的mypy不够强大,无法准确地键入数字塔。在

这种情况今天可能已经解决了,但这或多或少都是没有意义的,因为mypy最近实现了对协议的实验性支持(例如结构类型)!事实证明,这正是我们需要解决的问题,并最终修复数字塔(一旦协议被添加到pep484和输入模块中)。在

目前,您需要做的是:

  1. 安装typing_extensions模块(python3 -m pip install typing_extensions
  2. 从Github安装最新版本的mypy(运行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运行此命令将生成以下输出:

^{2}$

相关问题 更多 >