Search code examples
pythonpropertiespython-dataclasses

Is there really a bug in using python dataclass and property together?


I have been here:

and could not find a direct answer to why this simple code works fine...

class Vehicle:
    
    def __init__(self, wheels: int = 10):
        self.wheels = wheels # -> calls the setter method
    
    @property
    def wheels(self) -> int:
        print("getting wheels")
        return self._wheels
    
    @wheels.setter
    def wheels(self, wheels: int):
        print("setting wheels to", wheels)
        self._wheels = wheels

v = Vehicle() 
number = v.wheels # -> calls the getter method
print(number)

# last output: 10

...but this one does not (using dataclass):

from dataclasses import dataclass

@dataclass
class Vehicle:
    
    wheels: int = 10
    
    @property
    def wheels(self) -> int:
        print("getting wheels")
        return self._wheels
    
    @wheels.setter
    def wheels(self, wheels: int):
        print("setting wheels to", wheels)
        self._wheels = wheels

v = Vehicle() 
number = v.wheels
print(number)

# output: <property object at 0x000002042D70DDB0>

even when the official documentation of dataclass tells explicitly in the beginning that the decorator @dataclass adds exactly the __init__ method from the first code, i.e., this code:

@dataclass
class Vehicle:
    wheels: int = 10

should add this __init__:

def __init__(self, wheels: int = 10):
    self.wheels = wheels

Is that really a bug?


Short Note:

The private attribute _wheels is accessed only inside the property's methods, as it should be (to isolate it).

I found in others threads (listed above) that this attribute is manipulated outside the methods, exposed as 'public', which is not desired in some cases.


Solution

  • That's a bug in your code.

    This code:

    wheels: int = 10
    

    sets wheels to 10, then this code immediately after:

    @property
    def wheels(self) -> int:
        print("getting wheels")
        return self._wheels
    

    sets wheels to a property instance.

    @dataclass can't see the 10. Your 10 is gone. The annotation remains, so @dataclass creates a wheels field, but your field's default value is a property instance, not 10.