在 Python 数据类中使用描述符作为字段的正确方法是什么?
What is the proper way to use descriptors as fields in Python dataclasses?
我一直在研究 python 数据classes 并且想知道:制作一个或某些字段的最优雅或最 pythonic 的方法是什么描述符?
在下面的示例中,我定义了一个 Vector2D class,应该根据其长度进行比较。
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
虽然此代码有效,但每次创建实例时它都会触及 class,是否有 更具可读性/更少 hacky/更有意 的数据使用方式classes 和描述符在一起?
只需将 length 作为 属性 而不是字段即可,但这意味着我必须编写 __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
引入空操作 setter 是必要的,因为显然数据 class 创建的初始化方法试图分配给 length,即使没有' t 一个默认值,它明确设置 init=False
...
肯定有更好的方法吧?
可能无法回答您的确切问题,但您提到您不想将长度作为 属性 而不是字段的原因是因为您必须
write __lt__
, et.al by myself
虽然您必须自己实施 __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__
的相等方法
我认为您提供的示例不是您尝试做的事情的良好用例。尽管如此,为了完整起见,这里是您问题的可能解决方案:
@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
将默认值设置为 class 属性的值,除非该值是列表、字典或集合。
我一直在研究 python 数据classes 并且想知道:制作一个或某些字段的最优雅或最 pythonic 的方法是什么描述符?
在下面的示例中,我定义了一个 Vector2D class,应该根据其长度进行比较。
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
虽然此代码有效,但每次创建实例时它都会触及 class,是否有 更具可读性/更少 hacky/更有意 的数据使用方式classes 和描述符在一起?
只需将 length 作为 属性 而不是字段即可,但这意味着我必须编写 __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
引入空操作 setter 是必要的,因为显然数据 class 创建的初始化方法试图分配给 length,即使没有' t 一个默认值,它明确设置 init=False
...
肯定有更好的方法吧?
可能无法回答您的确切问题,但您提到您不想将长度作为 属性 而不是字段的原因是因为您必须
write
__lt__
, et.al by myself
虽然您必须自己实施 __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__
我认为您提供的示例不是您尝试做的事情的良好用例。尽管如此,为了完整起见,这里是您问题的可能解决方案:
@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
将默认值设置为 class 属性的值,除非该值是列表、字典或集合。