I think the simpler route to go there (allow the use of `self.attr=` until after ___post_init__ is run is simply to add another flag attribute, that tells wether "initialization is over", and respect that flag in the added `__setattr__`. The hook calling `__post_init__` would then set that flag after it is done.
A 5 line change is enough to make this work for __post_init__: ``` diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 91c1f6f80f..65d51ca0a9 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -194,6 +194,11 @@ _PARAMS = '__dataclass_params__' # __init__. _POST_INIT_NAME = '__post_init__' + +# Flag used to allow field initialization in `__post_init__` +# and `__init__` for frozen classes +_FROZEN_INITIALIZATION_FLAG = '__dataclass_initialized__' + # String regex that string annotations for ClassVar or InitVar must match. # Allows "identifier.identifier[" or "identifier[". # https://bugs.python.org/issue33453 for details. @@ -517,6 +522,9 @@ def _init_fn(fields, frozen, has_post_init, self_name): if f._field_type is _FIELD_INITVAR) body_lines.append(f'{self_name}.{_POST_INIT_NAME}({params_str})') + if frozen: + body_lines.append(f'{self_name}.{_FROZEN_INITIALIZATION_FLAG} = True') + # If no body lines, use 'pass'. if not body_lines: body_lines = ['pass'] @@ -552,13 +560,13 @@ def _frozen_get_del_attr(cls, fields): fields_str = '()' return (_create_fn('__setattr__', ('self', 'name', 'value'), - (f'if type(self) is cls or name in {fields_str}:', + (f'if (type(self) is cls or name in {fields_str}) and getattr(self, "{_FROZEN_INITIALIZATION_F LAG}", False):', ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', f'super(cls, self).__setattr__(name, value)'), globals=globals), _create_fn('__delattr__', ('self', 'name'), - (f'if type(self) is cls or name in {fields_str}:', + (f'if (type(self) is cls or name in {fields_str}) and getattr(self, "{_FROZEN_INITIALIZATION_F LAG}", False):', ' raise FrozenInstanceError(f"cannot delete field {name!r}")', f'super(cls, self).__delattr__(name)'), globals=globals), (END) ``` To get it working for `__init__` as well, however is a bit more complicated - as we don't have control of the dataclass metaclass, the only way to put a hook to set the flag after __init__ is run is to apply a decorator in the existing __init__ that would do it. However, this might be a way to have this feature _IF_ people think it would be worth it - (I personally would prefer yet another parameter to allow changing fields during frozen initialization, as that is, for me, an exceptional way of doing it less simple than having to call `object.__setattr__`) On Fri, 13 Dec 2019 at 07:22, Arthur Pastel <arthur.pas...@gmail.com> wrote: > On Thu, Dec 12, 2019 at 4:37 PM Eric V. Smith <e...@trueblade.com> wrote: > >> On 12/12/2019 8:50 AM, Arthur Pastel wrote: >> >> On 12/12/2019 8:07 AM, Arthur Pastel wrote: >>> >>> On Thu, Dec 12, 2019 at 2:03 AM Eric V. Smith <e...@trueblade.com> >>>> wrote: >>>> >>>>> On 12/11/2019 6:36 PM, Arthur Pastel wrote: >>>>> >> Add an extra hidden attribute to every instance just >>>>> >> to track whether you’re inside __post_init__ so __setattr__ can >>>>> check it? >>>>> I don't want to add attributes that weren't defined in the class >>>>> definition. >>>>> >>>> Would it be okay if we create an instance attribute before calling the >>>> __init__ and then we remove it after the __post_init__ ? >>>> Then we would simply have to check for the attribute existence when >>>> checking if we raise a FrozenInstanceError. >>>> >>>> That wouldn't work if the class used __slots__. >>>> >>> >>> Then, in this specific case, would it be possible to add the attribute >>> to the __slots__ in the generated class ? >>> >>> No, __slots__ can't be added or modified after the class exists (which >>> is why @dataclass doesn't have an option to add __slots__). >>> >> >> Arg, okay i didn't know. >> Then would it be possible to return a child class in the _process_class >> function ? It would then be possible to modify the slots. >> I'm trying to find a way around this but maybe this is not the good >> approach. >> >> Yes, it is possible. In my github repo I have an add_slots decorator that >> does just that. But I don't want the stdlib dataclass decorator to start >> returning a different class, without some serious discussion and some >> upsides. I don't think making __post_init__ prettier for frozen classes >> meets that bar. Although if there's overwhelming support for it here, I >> could be convinced to change my mind. >> > > Then what about at least allowing assignment for classes not having > __slots__ ? > _______________________________________________ > Python-ideas mailing list -- python-ideas@python.org > To unsubscribe send an email to python-ideas-le...@python.org > https://mail.python.org/mailman3/lists/python-ideas.python.org/ > Message archived at > https://mail.python.org/archives/list/python-ideas@python.org/message/SGMX3P352PYYJYLZYZZTQRG6UDVYK5IE/ > Code of Conduct: http://python.org/psf/codeofconduct/ >
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/C2NMBULJJSYYHL2XO5R26F4YD3OURYOS/ Code of Conduct: http://python.org/psf/codeofconduct/