4

I'd like to use @dataclass to remove a lot of boiler plate, but I also like the data encapsulation offered by @property. Can I do both simultaneously?

As a toy example, I have a class like

class Breakfast:

    def __init__(self, sausage: str, eggs: str = "Scrambled", coffee: bool = False):
        self._sausage = sausage
        self._eggs = eggs
        self._coffee = coffee

    @property
    def sausage(self):
        return self._sausage

    @property
    def eggs(self):
        return self._eggs

    @property
    def coffee(self):
        return self._coffee

    def __repr__(self):
        ...

    def __eq__(self):
        ...

where I may also have setters for some of properties. What I'd like, is to write this in some form like this

@dataclass(property=True)
class DataBreakfast:
    sausage: str
    eggs: str = "Scrambled"
    coffee: bool = False

(where, of course, my decorator argument doesn't work) that would do all the routine stuff that @dataclass does, and essentially outputs the code of the first snippet. I could then manually add setters in the rest of the class body at my leisure.

This seems like a common enough use case, but I couldn't figure out a way of getting it to work. The frozen parameter is the closest to what I want, but it doesn't really behave like @property as it precludes any kind of setter.

ScienceSnake
  • 608
  • 4
  • 15
  • What do you mean by "data encapsulation"? do you want attributes to be read-only by default? – Arne May 16 '21 at 13:25
  • @Arne, at a minimum, yes. But also leave myself the option to do something else (eg, increment a counter) every time an attribute is accessed or modified. – ScienceSnake May 16 '21 at 14:53
  • 1
    I can't find a way to write a solution that feels like it reduces the amount of work for your use-case. If all you want is read-only by default, there is `frozen`, or, of you only want it on some fields, [`semi`](https://stackoverflow.com/a/58628851/962190). If you want to skip writing the `@property`, and only want to add a getter sometimes, you're out of luck because you're going to get a type-error while the class-body is parsed, even before the dataclass-decorator gets to apply its logic. And if you want to add logic like an access-counter, you'll have to write the property anyway. – Arne May 17 '21 at 08:06
  • 1
    The only thing that I can get to work reasonably is "use properties under the hood, but without any special code inside. and if you try to mess with it, like adding a getter retroactively, everything will break", which doesn't seem useful at all. – Arne May 17 '21 at 08:07
  • Okay, thanks for answer. I guess I'll keep it simple and just not use ```@dataclass``` then and just keep doing my ```@property``` by hand. I have to admit I'm kind of surprised that this quasi-impossible, I would have thought that what I wanted would be a very common thing indeed. – ScienceSnake May 17 '21 at 10:26
  • 2
    Just in case you haven't stumbled over it yet, [here](https://florimond.dev/en/posts/2018/10/reconciling-dataclasses-and-properties-in-python/) is a blog post on using dataclasses with properties. Using a dataclass doesn't reduce the amount of work there, but you still do get all the other things like `__repr__` for free. – Arne May 17 '21 at 11:34
  • @ScienceSnake: What exactly would the effect or meaning be of `@dataclass(property=True)`? – martineau May 18 '21 at 11:55
  • @martineau Essentially, it would turn the second code snipet in the question into the first. In other words, every field ```foo``` defined in the class in the usual ```@dataclass``` manner would become a ```_foo``` attribute and a method ```@property def foo(self): return self._foo``` would be added. This could be done per ```Field``` separately too. The user would be free to define manually any ```@foo.setter``` they want (unless the ```frozen``` tag is used, potentially). Does this makes sense? I'm no expert, but it doesn't seem particularly unfeasible to me. – ScienceSnake May 18 '21 at 13:16
  • I think it would be possible to have properties with getters defined automatically, but am unsure about allowing optional setters to be defined in the body (if I'm understanding your reply correctly). – martineau May 18 '21 at 13:58
  • Sorry, I meant optional setters as in the user just writes their own methods in the class body, outside of what ```@dataclass``` does. Nothing done automatically and no extra optional parameter for Field for this. I've had a look at the dataclasses source code, but that is way beyond my ability to digest, let alone try and modify. – ScienceSnake May 18 '21 at 14:06
  • That's what I meant, too, as far as option setters goes — and that's the part I not sure it possible (the rest _is_, I believe). – martineau May 18 '21 at 14:09
  • Huh, I would have thought that that would have been the easy part, just letting the user override some default behaviour. I'm clearly way out of my depth here. Should I raise this as a feature request somewhere, or is it too niche and will never happen unless I do it myself? – ScienceSnake May 18 '21 at 14:24
  • 1
    Sounds like you want to use field properties. I'd take a look at this article as it might provide you some clarification: https://dataclass-wizard.readthedocs.io/en/latest/using_field_properties.html – rv.kvetch Aug 30 '21 at 16:33
  • 1
    Apologies, jsut going through and re-reading the section above. only issue I see with auto-property approach is that IDE won't know what `@foo.setter` means (won't be able to offer any completion hints). Of course its simple enough to define your own metaclass that automatically converts every dataclass field to a property, and would likely be the approach that I suggest for now (unless `dataclasses` adds this kind of support somewhere down the road) – rv.kvetch Aug 30 '21 at 17:30

0 Answers0