omegaconf: Structured configs do not respect `dataclasses.field(init=False)`
The documentation of dataclasses has this example for init=False:
@dataclass
class C:
a: float
b: float
c: float = field(init=False)
def __post_init__(self):
self.c = self.a + self.b
It just means that c is not an argument to the __init__. I find this useful sometimes when I have a configuration value that can be computed from other configuration values.
Describe the solution you’d like
OmegaConf should just ignore fields that have init=False set.
Describe alternatives you’ve considered
One alternative is to not give a type annotation to the field and just set it to MISSING:
>>> @dataclass
... class C:
... a: float
... b: float
... c = MISSING
... def __post_init__(self):
... self.c = self.a + self.b
...
>>> d = OmegaConf.structured(C)
>>> d.a = 3
>>> d.b = 4
>>> o = OmegaConf.to_object(d)
>>> o.c
7.0
This works, but it is a bit sad that we have to accept the loss of the type annotation.
Additional context
This is what currently happens when you use init=False.
>>> from dataclasses import dataclass, field
>>> @dataclass
... class C:
... a: float
... b: float
... c: float = field(init=False)
... def __post_init__(self):
... self.c = self.a + self.b
...
>>> c = C(a=3, b=4)
>>> c.c
7
>>> from omegaconf import OmegaConf
>>> d = OmegaConf.structured(C)
>>> d.a = 3
>>> d.b = 4
>>> OmegaConf.to_object(d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/omegaconf.py", line 574, in to_object
return OmegaConf.to_container(
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/omegaconf.py", line 553, in to_container
return BaseContainer._to_content(
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/basecontainer.py", line 249, in _to_content
return conf._to_object()
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/dictconfig.py", line 735, in _to_object
self._format_and_raise(
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/base.py", line 190, in _format_and_raise
format_and_raise(
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/_utils.py", line 821, in format_and_raise
_raise(ex, cause)
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/_utils.py", line 719, in _raise
raise ex.with_traceback(sys.exc_info()[2]) # set end OC_CAUSE=1 for full backtrace
omegaconf.errors.MissingMandatoryValue: Structured config of type `C` has missing mandatory value: c
full_key: c
object_type=C
>>> d.c = 0
>>> OmegaConf.to_object(d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/omegaconf.py", line 574, in to_object
return OmegaConf.to_container(
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/omegaconf.py", line 553, in to_container
return BaseContainer._to_content(
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/basecontainer.py", line 249, in _to_content
return conf._to_object()
File "/home/tmk/.conda/envs/palbolts/lib/python3.9/site-packages/omegaconf/dictconfig.py", line 752, in _to_object
result = object_type(**field_items)
TypeError: __init__() got an unexpected keyword argument 'c'
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 1
- Comments: 17 (5 by maintainers)
Great! FYR most of the
to_objecttests are here.Ah, thanks for tackling this!
Great, I will take a swing at this; hopefully relatively soon 😄
@thomkeh I think using
@propertyis an excellent alternative to using__post_init__+init=False, and it avoids the issues brought up by @rsokl.By the way @thomkeh, I’ve discovered a workaround for your original issue:
The
obj.cattribute behaves “like aninit=Falsefield”.@rsokl:
Your suggestion to call
MyDataClass.__init__withinit_field_items, followed by callingsetattrfor each of thenon_init_field_items, feels very natural, and I would like to move forward with it.I feel that a provision should be made for the case where an
init=Falsefield has the special valueomegaconf.MISSING, in which case it makes sense to skip callingsetattr. Here is the motivating example:If you are inclinded to submit a PR implementing this, it would be most welcome! Otherwise, I will put it on my docket.
The
__post_init__problemAgreed.
You’ve pointed out that strange things can occur if
__post_init__gets run twice, specifically if__post_init__changes the value ofinit=Truefields (e.g. as in yourBadGuyexample above). I think using__post_init__to change the value of aninit=Truefield is a strange thing to do, and I’m not sure what use-case would require such logic.Let me point out that, to ensure
__post_init__is run only once, one can callOmegaConf.createon the dataclass itself (rather than on a dataclass instance):This is the pattern that should be used e.g. if
__post_init__has side-effects and must be invoked exactly once.I understand your view that attributes of structured DictConfig instances should correspond closely to attributes of the dataclass instances returned from
to_object. That being said, I fear that undoing the effects of a call to__post_init__by invokingsetattrcould lead to confusion. In the absence of a motivating use-case, I think for now we should preserve the current behavior ofOmegaConf.to_objectin this case where__post_init__is defined and where all fields haveinit=True.The case where
__post_init__is defined and some fields haveinit=False:I think we agree that, in this case,
OmegaConf.to_objectshould raise an error.FWIW, since opening this issue, I have realized that my use case was better handled by
@propertyor even@cached_property(added in python 3.8). So, consider my suggestion retracted.(And I don’t understand @rsokl 's requirements well enough to comment on his proposal.)
Hi, I am also registering the interest in excluding
init=False. We are creating a framework within PyTorch3D where we try to unify the definitions of Pytorch modules and structured configs. Thus, the same class is passed to structured() and then instantiated in the code. It is useful to annotate types for dynamic fields, andinit=Falseis generally fulfils that purpose.Hi @thomkeh , thanks for the feature request!
What is the motivation for ignoring fields that have
init=False? Is this so that the dataclass’s__post_init__method will be able to initialize that field later (when it’s time to initialize a dataclass instance using the DictConfig’s data)?EDIT: Here is a WORKAROUND:
The
obj.cattribute behaves “like aninit=Falsefield”.