Using dataclasses with dependent attributes via property

前端 未结 1 637
挽巷
挽巷 2021-01-07 03:19

I have a class, for example Circle, which has dependent attributes, radius and circumference. It makes sense to use a dataclass<

相关标签:
1条回答
  • 2021-01-07 03:34

    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.

    0 讨论(0)
提交回复
热议问题