Defining the following subclass:
from dataclasses import dataclass, replace, field, InitVar
@dataclass
class MyDataClass:
foo: InitVar[str]
bar: str
foo_len: int = field(init=False)
def __post_init__(self, foo: int):
self.foo_len = len(foo)
And creating an instance of it:
instance = MyDataClass(foo="foo", bar="bar")
Trying to call replace on the instance fails:
In[5]: replace(instance, bar="baz")
Traceback (most recent call last):
File "/home/or/.venv/m/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 3343, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-5-5de186c91dc9>", line 1, in <module>
replace(instance, bar="baz")
File "/home/or/.venv/m/lib/python3.6/site-packages/dataclasses.py", line 1170, in replace
changes[f.name] = getattr(obj, f.name)
AttributeError: 'MyDataClass' object has no attribute 'foo'
As far as I understand, InitVars should exist during init only, but from some investigation, I can see they are put into __dataclass_fields__ and therefore replace is trying to use them.
I'm using Python 3.6, so my dataclasses package is the backport, not the built-in package for Python 3.7+.
I found this line in dataclasses' documentation:
Init-only variables without default values, if any exist, must be specified on the call to replace() so that they can be passed to init() and post_init().
which means if I had the original value of instance.foo, I could theoretically do:
replace(instance, bar="baz", foo="value of foo")
But I don't have the original value of instance.foo.
How can I avoid this error if I want to create a copy of an existing instance of a dataclass?
Having InitVars in your dataclass means that you can't call the constructor without explicitly passing the InitVar again. This disqualifies the usage of both replace(instance, ...) as well as MyDataClass(**asdict(instance), ...) if you don't have access to the InitVar any more.
If all you care about is getting a valid copy you can use the standard-librarie's copy.copy though, (or copy.deepcopy for dataclasses that have their own containers, or are otherwise nested) which does not call the instance's constructor:
>>> from copy import copy
>>> instance_a = MyDataClass(foo="foo", bar="bar")
>>> instance_b = copy(instance_a)
>>> instance_b
MyDataClass(bar='bar', foo_len=3)
>>> instance_a is instance_b
False
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With