> On 13 Dec 2019, at 14:57, Arthur Pastel <arthur.pas...@gmail.com> wrote: > > >> 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 > > > We discussed this just before and having an extra instance attribute was > quite problematic. > That's why I suggested to have an attribute set in the beginning of the > __init__ and deleted after the initialization is complete. > > Anyways in both cases, there is still a problem when the class uses > __slots__as @Eric V. Smith mentioned previously.
Hack warning: Put the flag outside of the class? Say by putting the instance id() into a set(). Now it works for slots, just need to think about data races... Barry > > >> On Fri, Dec 13, 2019 at 2:50 PM Joao S. O. Bueno <jsbu...@python.org.br> >> wrote: >> 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/FIKYAHWRJDV2VVTKHG5KLYFF3YSX5T46/ > 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/S7OFQAOZHRNZLPQHV6VTEUHDBJDNYC7A/ Code of Conduct: http://python.org/psf/codeofconduct/