#python #python-dataclasses #python-descriptors
Вопрос:
Я играл с датаклассами python и задавался вопросом: каков самый элегантный или самый питонический способ создания одного или нескольких дескрипторов полей?
В приведенном ниже примере я определяю класс Vector2D, который следует сравнивать по его длине.
from dataclasses import dataclass, field
from math import sqrt
@dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(init=False)
def __post_init__(self):
type(self).length = property(lambda s: sqrt(s.x**2 s.y**2))
Vector2D(3,4) > Vector2D(4,1) # True
Хотя этот код работает, он затрагивает класс каждый раз, когда создается экземпляр, существует ли более читаемый / менее хакерский / более подходящий способ совместного использования классов данных и дескрипторов?
Просто наличие длины в качестве свойства , а не поля, будет работать, но это означает, что я должен написать __lt__
, et.al… сам по себе.
Другое решение, которое я нашел, столь же непривлекательно:
@dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(init=False)
@property
def length(self):
return sqrt(self.x**2 self.y**2)
@length.setter
def length(self, value):
pass
Введение установщика без операций необходимо, поскольку, по-видимому, метод инициализации, созданный в классе данных, пытается присвоить длину, даже если значения по умолчанию нет и оно явно устанавливается init=False
…
Конечно, должен быть лучший способ, верно?
Комментарии:
1. Мне интересно, просто ли вы не пересекаете тонкую границу между классом данных, т. Е. какой — то тонкой оболочкой вокруг данных, и правильным классом, представляющим сущность с операциями над собой -в вашем случае Вектор ( пример Unity3D ).
2. Я бы задал вопрос, хотите ли вы сделать этот класс упорядоченным. Рассмотрим
order=True
этоeq=True
, но вы, вероятно, не хотите считать два вектора равными, если они имеют одинаковую длину. Учтите, что вы все еще можете сортировать элементы на основеlength
чего-то подобногоsorted(vectors, key=lambda v: v.length)
и аналогично для других целей. В этом случае я бы сохранилlength
как свойство, а не как поле.3. «Более читаемый / менее хакерский способ» — это правильная реализация
__lt__
, ИМО. Любому, кто читает ваш код, становится намного яснее, как именно сравниваются дваVector2D
экземпляра. И таким образом, вам больше не понадобятсяlength
поле класса данных иlength.setter
метод, толькоlength
свойство.4. Я впечатлен быстрыми ответами, и я признаю, что вы поднимаете обоснованные вопросы. Вероятно, включение этого в класс данных только для того, чтобы сделать определение класса более кратким, на самом деле не улучшит читабельность.
Ответ №1:
Возможно, я не отвечу на ваш точный вопрос, но вы упомянули, что причина, по которой вы не хотели иметь длину в качестве свойства и поля «не», заключалась в том, что вам пришлось бы
напиши
__lt__
, et.al сам по себе
Хотя вам действительно нужно реализовывать __lt__
самостоятельно, вы можете на самом деле обойтись без реализации именно этого
from functools import total_ordering
from dataclasses import dataclass, field
from math import sqrt
@total_ordering
@dataclass
class Vector2D:
x: int
y: int
@property
def length(self):
return sqrt(self.x ** 2 self.y ** 2)
def __lt__(self, other):
if not isinstance(other, Vector2D):
return NotImplemented
return self.length < other.length
def __eq__(self, other):
if not isinstance(other, Vector2D):
return NotImplemented
return self.length == other.length
print(Vector2D(3, 4) > Vector2D(4, 1))
Причина, по которой это работает, заключается в том, что total_ordering
просто добавляет все другие методы равенства, основанные на __eq__
и __lt__
Комментарии:
1. В вашем примере, возможно, неправильного сравнения, они действительно имеют одинаковую длину?
2. Да, это правда, я думаю, что это деталь реализации, если
Vector2D(0, 1) == Vector2D(1, 0)
3. Это желаемое поведение из OP: «В приведенном ниже примере я определяю класс Vector2D, который следует сравнивать по его длине».
4. Я не думал о чем-то подобном
total_ordering
. Хотя это не отвечает на точный вопрос, который я задал, это избавляет меня от множества избыточных функций сравнения и гораздо более читабельно, чем мой исходный код. Я соглашусь с таким подходом, большое спасибо!
Ответ №2:
Я не думаю, что приведенный вами пример является хорошим примером использования того, что вы пытаетесь сделать. Тем не менее, для полноты картины, вот возможное решение вашей проблемы:
@dataclass(order=True)
class Vector2D:
x: int = field(compare=False)
y: int = field(compare=False)
length: int = field(default=property(lambda s: sqrt(s.x**2 s.y**2)), init=False)
Это работает, потому dataclass
что устанавливает значения по умолчанию в качестве значений атрибутов класса, если только значение не является списком, диктом или набором.