Reputation: 378
I have recently learned about the benefits of dataclasses in reducing boilerplate code when writing classes that primarily act as data containers.
I would like to be able to use dataclasses but would like to maintain the level of encapsulation that I have gotten used to with regular classes using private/protected variables and/or properties.
I would like to be able to encapsulate two fields so that they are only ever updated together according to some expected rules.
as a minimum example:
class EncapsulatedClass:
def __init__(self, field1, field2):
validate_args(field1, field2)
self._field1 = field1
self._field2 = field2
def update_fields(self, arg1, arg2):
if arg1:
self._field1 += 1
if arg2:
self._field2 += 1
if arg1 and arg2:
self._field1 = 0
The reason I would like this behaviour is that it guarantees (up to user modification of protected args) that field1 and field2 have some expected relationship, this can be validated on initialisation and then never needs to be validated again.
However, with a standard class, I need to implement __eq__
__repr__
and __init__
which I would rather avoid to reduce boilerplate.
Is there a way to achieve this kind of behaviour using dataclasses with minimal boilerplate?
Upvotes: 2
Views: 1596
Reputation: 530960
In general, no. But depending on exactly what you are trying to do, there may be various specific workarounds. For example, here I might make a property that operates on a tuple.
class EncapsulatedClass:
def __init__(self, field1, field2):
self.field = (field1, field2)
@property
def field(self):
return (self._field1, self._field2)
@field.setter
def field(self, value):
v1, v2 = value # Here, just letting a possible exception propogate
if v1:
self._field1 += 1
if v2:
self._field2 += 1
if v1 and v2:
self._field1 = 0
# Now it's a wrapper around the setter
def update_fields(self, field1, field2):
self.field = (field1, field2)
You can continue using a property in a dataclass. __post_init__
can be defined to initialize the property without having to re-impelement everything that would use the underlying attributes.
@dataclass
class EncapsulatedClass:
_field1: int
_field2: int
def __post_init__(self):
self.field = (self._field1, self._field2)
@property
def field(self):
return (self._field1, self._field2)
@field.setter
def field(self, value):
v1, v2 = value # Here, just letting a possible exception propogate
if v1:
self._field1 += 1
if v2:
self._field2 += 1
if v1 and v2:
self._field1 = 0
def update_fields(self, field1, field2):
self.field = (field1, field2)
Upvotes: 1