通过属性使用具有依赖属性的数据类 [英] Using dataclasses with dependent attributes via property

查看:79
本文介绍了通过属性使用具有依赖属性的数据类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个类,例如Circle,它具有相关属性radiuscircumference.在这里使用dataclass是有意义的,因为__init____eq____repr__的样板和排序方法(__lt__,...).

I have a class, for example Circle, which has dependent attributes, radius and circumference. It makes sense to use a dataclass here because of the boilerplate for __init__, __eq__, __repr__ and the ordering methods (__lt__, ...).

我选择其中一个属性来依赖另一个属性,例如圆周是根据半径计算得出的.由于该类应支持使用两个属性之一进行初始化(+将它们包含在__repr__dataclasses.asdict中),因此我都对这两个注释了:

I choose one of the attributes to be dependent on the other, e.g. the circumference is computed from the radius. Since the class should support initialization with either of the attributes (+ have them included in __repr__ as well as dataclasses.asdict) I annotate both:

from dataclasses import dataclass
import math


@dataclass
class Circle:
    radius: float = None
    circumference: float = None

    @property
    def circumference(self):
        return 2 * math.pi * self.radius

    @circumference.setter
    def circumference(self, val):
        if val is not type(self).circumference:  # <-- awkward check
            self.radius = val / (2 * math.pi)

这需要我为if val is not type(self).circumference添加一些笨拙的检查,因为如果没有为__init__提供任何值,则设置者将收到此检查.

This requires me to add the somewhat awkward check for if val is not type(self).circumference because this is what the setter will receive if no value is provided to __init__.

然后,如果我想通过声明frozen=True使该类可哈希化,则需要将self.radius = ...更改为object.__setattr__(self, 'radius', ...),因为否则,它将尝试分配给冻结实例的字段.

Then if I wanted to make the class hashable by declaring frozen=True I need to change self.radius = ... to object.__setattr__(self, 'radius', ...) because otherwise this would attempt to assign to a field of a frozen instance.

所以我的问题是,这是同时使用数据类和属性的明智方式,还是潜在的(非显而易见的)障碍摆在眼前,在这种情况下我应该避免使用数据类?也许还有更好的方法可以实现这一目标?

So my question is if this is a sane way of using dataclasses together with properties or if potential (non-obvious) obstacles lie ahead and I should refrain from using dataclasses in such cases? Or maybe there is even a better way of achieving this goal?

推荐答案

对于初学者,您可以在__init__方法中设置属性,如下所示:

For starters, you could set the attributes in the __init__ method as follows:

from dataclasses import dataclass, InitVar
import math


@dataclass(frozen=True, order=True)
class CircleWithFrozenDataclass:
    radius: float = 0
    circumference: float = 0

    def __init__(self, radius=0, circumference=0):
        super().__init__()
        if circumference:
            object.__setattr__(self, 'circumference', circumference)
            object.__setattr__(self, 'radius', circumference / (2 * math.pi))
        if radius:
            object.__setattr__(self, 'radius', radius)
            object.__setattr__(self, 'circumference', 2 * math.pi * radius)

这仍将为您提供所有有用的__eq____repr____hash__和订购方法注入.尽管object.__setattr__看起来很丑,但请注意 CPython实现在为冻结的dataclass注入生成的__init__方法时,它本身使用object.__setattr__ 设置属性.

This will still provide you with all the helpful __eq__, __repr__, __hash__, and ordering method injections. While object.__setattr__ looks ugly, note that the CPython implementation itself uses object.__setattr__ to set attributes when injecting the generated __init__ method for a frozen dataclass.

如果您真的想摆脱object.__setattr__,则可以设置frozen=False(默认值)并自己覆盖__setattr__方法.这是在复制数据类的frozen功能如何在CPython中实现.请注意,由于frozen=False之后不再插入__hash__,因此您还必须打开unsafe_hash=True.

If you really want to get rid of object.__setattr__, you can set frozen=False (the default) and override the __setattr__ method yourself. This is copying how the frozen feature of dataclasses is implemented in CPython. Note that you will also have to turn on unsafe_hash=True as __hash__ is no longer injected since frozen=False.

@dataclass(unsafe_hash=True, order=True)
class CircleUsingDataclass:
    radius: float = 0
    circumference: float = 0

    _initialized: InitVar[bool] = False

    def __init__(self, radius=0, circumference=0):
        super().__init__()
        if circumference:
            self.circumference = circumference
            self.radius = circumference / (2 * math.pi)
        if radius:
            self.radius = radius
            self.circumference = 2 * math.pi * radius
        self._initialized = True

    def __setattr__(self, name, value):
        if self._initialized and \
            (type(self) is __class__ or name in ['radius', 'circumference']):
            raise AttributeError(f"cannot assign to field {name!r}")
        super().__setattr__(name, value)

    def __delattr__(self, name, value):
        if self._initialized and \
            (type(self) is __class__ or name in ['radius', 'circumference']):
            raise AttributeError(f"cannot delete field {name!r}")
        super().__delattr__(name, value)

我认为默认情况下,冻结仅应在__init__之后进行,但现在我可能会使用第一种方法.

In my opinion, freezing should only happen after the __init__ by default, but for now I will probably use the first approach.

这篇关于通过属性使用具有依赖属性的数据类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆