Объявление длины кортежей в наборе Python - PullRequest
3 голосов
/ 25 марта 2019

Мне интересно, подчиняться ли Tuple [float, ...], даже если я знаю длину кортежа.

У меня есть класс Point и Rect, а также свойство aspoints в классе Rect, которое должно возвращать кортеж верхнего левого и нижнего правого углов в виде двух кортежей.

Итератор имеет тип 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]]")

Ответы [ 4 ]

3 голосов
/ 25 марта 2019

Вы можете расширить функцию 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 голосов
/ 25 марта 2019

Кажется, проблема в том, что ваши знания о том, сколько элементов вернет итератор, не могут быть закодированы в статический тип.

Я не уверен, что перегрузка __iter__ для итерации по фиксированномуЗдесь лучше всего использовать число элементов, так как вы в основном используете его как уловку, чтобы разрешить приведение объекта к кортежу.

Возможно, имеет смысл добавить что-то вроде метода "to_tuple ()"вместо класса данных Point?Вы могли бы объявить тип там ...

Редактировать: поочередно, я думаю, вы могли бы деструктурировать вывод итератора, но вы все еще не сохраняете много кода:

a, b = self.tl
c, d = self.br
return (a, b), (c, d)
1 голос
/ 25 марта 2019

Другой подход: вы действительно не хотите перебирать Point; вам просто нужен способ получить кортеж float с, и вы только делаете свою точку повторяемой, чтобы вы могли напрямую передать ее в tuple. (Подумайте об этом: вы когда-нибудь написали бы код, похожий на for coord in Point(3, 5): ...?)

Вместо определения __iter__ определите функцию, которая делает то, что вам действительно нужно: возвращает пару float с.

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 голосов
/ 26 марта 2019

Учитывая требования, кажется, что использование 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.

...