I'm wondering whether or not to submit to Tuple[float, ...] even though I know the length of the tuple.
I have a Point and a Rect class, and a property aspoints
in the Rect class that is to return a tuple of the top-left and bottom-right corners, as two-tuples.
The iterator has type Iterator[float]
, which I know will give me two floats. I want the return value of the property to be
Tuple[Tuple[float, float], Tuple[float, float]]
because I know that the iterator will give me two floats for each point.
Should I submit, and just say that it will return a Tuple[Tuple[float, ...], Tuple[float, ...]]
, leaving a comment in the documentation of the length of them, or is there a better solution?
Here's the code.
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)
The problem occurs in Rect.aspoints. From MyPy I get the following error:
error: Incompatible return value type (got "Tuple[Tuple[float, ...], Tuple[float, ...]]", expected "Tuple[Tuple[float, float], Tuple[float, float]]")
Python tuple method len() returns the number of elements in the tuple.
To find the size of a tuple in Python, We use the len() function in Python. The length function is used to find the length of a tuple. It's calculating the size of a tuple; It takes one parameter, it's a tuple in our case and returns a total length of a tuple by counting the tuple's number of elements.
The len() method returns the number of elements in the tuple.
typing. Tuple is special here in that it lets you specify a specific number of elements expected and the type of each position. Use ellipsis if the length is not set and the type should be repeated: Tuple[float, ...] describes a variable-length tuple with float s. For typing.
You can extend your aspoints
function to cast the field types correctly:
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
You can also fit everything in one line, but the readability suffers a lot from it. The cast
function doesn't do anything during runtime, it just serves as an explicit way of telling mypy (or some other static type checker) that you know more about your types than can be expressed in the basic typing tools.
You should absolutely not change the return type of __iter__
to be anything that is not an iterator, that would be very strange and confusing.
The problem seems to be that your knowledge of how many elements the iterator will return can't be encoded into a static type.
I'm not sure that overloading __iter__
to iterate over a fixed number of elements is the best approach here, since you're basically using it as a trick to allow casting an object to a tuple.
Maybe it would make sense to add something like a "to_tuple()" method to the Point data class instead? You could declare the type there...
Edit: alternately, I guess you could destructure the output of the iterator, but you're still not saving a lot of code:
a, b = self.tl
c, d = self.br
return (a, b), (c, d)
Another approach: You don't really want to iterate over a Point
; you just want a way to get a tuple of float
s and you are only making your point iterable so that you can pass it to tuple
directly. (Consider this: would you ever write code that looks like for coord in Point(3, 5): ...
?)
Instead of defining __iter__
, define a function that does what you really want: returns a pair of 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()
In support of this approach, it also lets you write an additional method that also returns a pair of floats, but with different semantics:
def as_polar_pair(self) -> PairOfFloats:
return cmath.polar(complex(self.x, self.y))
Regarding unpacking, defining Point.__getitem__
rather than Point.__iter__
is sufficient:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With