在Python类型中声明元组的长度

问题描述 投票:3回答:4

我想知道是否提交给Tuple [float,...],即使我知道元组的长度。

我有一个Point和一个Rect类,以及Rect类中的属性aspoints,它返回左上角和右下角的元组,作为两元组。

迭代器有类型Iterator[float],我知道它会给我两个浮点数。我希望属性的返回值为Tuple[Tuple[float, float], Tuple[float, float]],因为我知道迭代器将为每个点提供两个浮点数。

我应该提交,并且只是说它将返回Tuple[Tuple[float, ...], Tuple[float, ...]],在文档中留下评论,或者是否有更好的解决方案?

这是代码。

from dataclasses import dataclass
from typing import Iterator, Tuple

@dataclass
class Point:
    x: float
    y: float

    def __iter__(self) -> Iterator[float]:
        return iter((self.x, self.y))

@dataclass
class Rect:
    x: float
    y: float
    width: float
    height: float

    @property
    def tl(self) -> Point:
        return Point(self.x, self.y)

    @property
    def br(self) -> Point:
        return Point(self.x + self.width, self.y + self.height)

    @property
    def aspoints(self) -> Tuple[Tuple[float, float], Tuple[float, float]]:
        return tuple(self.tl), tuple(self.br)

Rect.aspoints中出现此问题。从MyPy我收到以下错误:

error: Incompatible return value type (got "Tuple[Tuple[float, ...], Tuple[float, ...]]", expected "Tuple[Tuple[float, float], Tuple[float, float]]")

python python-3.x mypy static-typing
4个回答
3
投票

您可以扩展aspoints函数以正确转换字段类型:

def aspoints(self) -> Tuple[Tuple[float, float], Tuple[float, float]]:
    left = cast(Tuple[float, float], tuple(self.tl))
    right = cast(Tuple[float, float], tuple(self.br))
    return left, right

您也可以将所有内容都放在一行中,但可读性会受到很大影响。 cast函数在运行时没有做任何事情,它只是作为一种明确的方式告诉mypy(或其他一些静态类型检查器),你知道更多关于你的类型,而不是在基本的打字工具中表达。

你绝对不应该将__iter__的返回类型更改为不是迭代器的任何东西,这将是非常奇怪和令人困惑的。


3
投票

问题似乎是您对迭代器将返回多少元素的了解无法编码为静态类型。

我不确定重载__iter__来迭代固定数量的元素是最好的方法,因为你基本上将它用作允许将对象转换为元组的技巧。

也许在Point数据类中添加类似“to_tuple()”方法的东西是有意义的吗?你可以在那里声明类型......

编辑:或者,我猜你可以解构迭代器的输出,但你仍然没有保存很多代码:

a, b = self.tl
c, d = self.br
return (a, b), (c, d)

1
投票

另一种方法:你真的不想迭代Point;你只是想要一种方法来获得floats的元组,你只能使你的点可迭代,以便你可以直接传递给tuple。 (考虑一下:你会编写看起来像for coord in Point(3, 5): ...的代码吗?)

不要定义__iter__,而是定义一个能够做你真正想要的函数:返回一对floats。

from typing import Tuple


PairOfFloats = Tuple[float,float]


@dataclass
class Point:
    x: float
    y: float

    def as_cartesian_pair(self) -> PairOfFloats:
        return (self.x, self.y)


@dataclass
class Rect:
    x: float
    y: float
    width: float
    height: float

    @property
    def tl(self) -> Point:
        return Point(self.x, self.y)

    @property
    def br(self) -> Point:
        return Point(self.x + self.width, self.y + self.height)

    @property
    def aspoints(self) -> Tuple[PairOfFloats, PairOfFloats]:
        return self.tl.as_cartesian_pair(), self.br.as_cartesian_pair()

为了支持这种方法,它还允许您编写另一个方法,该方法也返回一对浮点数,但具有不同的语义:

def as_polar_pair(self) -> PairOfFloats:
    return cmath.polar(complex(self.x, self.y))

关于解包,定义Point.__getitem__而不是Point.__iter__就足够了:

def __getitem__(self, i) -> float:
    if i == 0:
        return self.x
    elif i == 1:
        return self.y
    else:
        raise IndexError


>>> p = Point(3,5)
>>> p[0], p[1]
(3, 5)
>>> x, y = p
>>> x
3
>>> y
5

0
投票

鉴于要求,似乎使用typing.NamedTuple作为Point的基类是最合适的。它会大大简化事情,因为没有使用dataclasses.dataclass特征,在给出的例子中Point似乎也不需要可变性。

from typing import Tuple, NamedTuple
from dataclasses import dataclass

class Point(NamedTuple):
    x: float
    y: float

@dataclass
class Rect:
    ...
    @property
    def aspoints(self) -> Tuple[Point, Point]:
        return (self.tl, self.br)

虽然我们在这里,但Rect似乎也可能更适合从typing.NamedTuple继承,原因与Point已经提到的相同。

© www.soinside.com 2019 - 2024. All rights reserved.