Using dataclasses with dependent attributes via property

元气小坏坏 提交于 2019-12-19 04:18:32

问题


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__, ...).

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)

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__.

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?


回答1:


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)

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.

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)

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



来源:https://stackoverflow.com/questions/57791679/using-dataclasses-with-dependent-attributes-via-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!