我想知道是否提交给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]]")
您可以扩展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__
的返回类型更改为不是迭代器的任何东西,这将是非常奇怪和令人困惑的。
问题似乎是您对迭代器将返回多少元素的了解无法编码为静态类型。
我不确定重载__iter__
来迭代固定数量的元素是最好的方法,因为你基本上将它用作允许将对象转换为元组的技巧。
也许在Point数据类中添加类似“to_tuple()”方法的东西是有意义的吗?你可以在那里声明类型......
编辑:或者,我猜你可以解构迭代器的输出,但你仍然没有保存很多代码:
a, b = self.tl
c, d = self.br
return (a, b), (c, d)
另一种方法:你真的不想迭代Point
;你只是想要一种方法来获得float
s的元组,你只能使你的点可迭代,以便你可以直接传递给tuple
。 (考虑一下:你会编写看起来像for coord in Point(3, 5): ...
的代码吗?)
不要定义__iter__
,而是定义一个能够做你真正想要的函数:返回一对float
s。
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
鉴于要求,似乎使用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
已经提到的相同。