git.net

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Property for dataclass field with default value


Ivan Ivanyuk wrote:

> Hello All,
> 
> I have some trouble using @dataclass together with @property decorator
> or property() function.
> 
> From the documentation and PEP is seems that the intended behaviour of
> @dataclass is to be the same as normal __init__() that sets instance
> variables.
> 
> But it seems that when using @property decorator some parts work
> differently when relying on default values. I'm using Pyhton 3.8.3 for
> this.
> 
> Using the code:
> 
> from dataclasses import dataclass
> 
> @dataclass
> class Container:
>     x: int = 30
> 
>     @property
>     def x(self) -> int:
>         return self._x
> 
>     @x.setter
>     def x(self, z: int):
>         if z > 1:
>             self._x = z
>         else:
>             raise ValueError
> c= Container(x=10)
> print(c)
> c= Container()
> print(c)
> 
> output is:
> 
> Container(x=10)
> Traceback (most recent call last):
>   File
>   "/Users/ivanivanyuk/Documents/Shared/repos/bitbucket/evbox/g5plus-
automated-testing/dataclass_example.py",
> line 18, in <module>
>     c= Container()
>   File "<string>", line 3, in __init__
>   File
>   "/Users/ivanivanyuk/Documents/Shared/repos/bitbucket/evbox/g5plus-
automated-testing/dataclass_example.py",
> line 13, in x
>     if z > 1:
> TypeError: '>' not supported between instances of 'property' and 'int'
> 
> Code with __init__() being inserted that should be roughly the same as
> generated by the @dataclass decorator:
> 
> @dataclass
> class Container:
>     x: int = 30
> 
>     def __init__(self, x:int = 30):
>         self.x = x
> 
>     @property
>     def x(self) -> int:
>         return self._x
> 
>     @x.setter
>     def x(self, z: int):
>         if z > 1:
>             self._x = z
>         else:
>             raise ValueError
> 
> c= Container(x=10)
> print(c)
> c= Container()
> print(c)
> 
> output is:
> Container(x=10)
> Container(x=30)
> 
> As far as I can see, this happens because actual __init__() generated
> by the @dataclass decorator looks like:
> 
>     def __init__(self, x:int = <property object at 0x106655db0>):
>         self.x = x
> 
> I came up with a really convoluted way to work around it (just for
> fun, as this clearly defies the idea of dataclasses as a way to
> decrease boilerplate code):
> 
> from dataclasses import dataclass, field
> 
> def set_property():
>     Container.x = property(Container.get_x, Container.set_x)
>     return 30
> 
> @dataclass
> class Container:
>     x: int = field(default_factory=set_property)
> 
>     def get_x(self) -> int:
>         return self._x
> 
>     def set_x(self, z: int):
>         if z > 1:
>             self._x = z
>         else:
>             raise ValueError
> 
> c= Container(x=10)
> print(c)
> c= Container()
> print(c)
> 
> output is:
> Container(x=10)
> Container(x=30)
> 
> So, what I'm missing here? Is there some way to use field() or
> decorators to make property just work the same as in non-decorated
> class?

Your class definition is basically

class Container:
    x = default_value
    x = property_x

i. e. you use the same name twice. A possible workaround might be to define 
the property in a subclass. That way you get distinct namespaces for the 
default value and the property:

@dataclass
class ContainerBase:
    x: int = 42

class Container(ContainerBase):
    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        if value <= 1:
            raise ValueError
        self._x = value