在 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 属性的值,除非该值是列表、字典或集合。