Dynamically add fields to dataclass objects

10,755

Solution 1

You could use make_dataclass to create X on the fly:

X = make_dataclass('X', [('i', int), ('s', str)])
x = X(i=42, s='text')

asdict(x)
# {'i': 42, 's': 'text'}

Or as a derived class:

@dataclass
class X:
    i: int

x = X(i=42)
x.__class__ = make_dataclass('Y', fields=[('s', str)], bases=(X,))
x.s = 'text'

asdict(x)
# {'i': 42, 's': 'text'}

Solution 2

As mentioned, fields marked as optional should resolve the issue. If not, consider using properties in dataclasses. Yep, regular properties should work well enough - though you'll have to declare field in __post_init__, and that's slightly inconvenient.

If you want to set a default value for the property so accessing getter immediately after creating the object works fine, and if you also want to be able to set a default value via constructor, you can make use of a concept called field properties; a couple libraries like dataclass-wizard provide full support for that.

example usage:

from dataclasses import asdict, dataclass
from typing import Optional

from dataclass_wizard import property_wizard


@dataclass
class X(metaclass=property_wizard):
    i: int
    s: Optional[str] = None

    @property
    def _s(self):
        return self._s

    @_s.setter
    def _s(self, s: str):
        self._s = s


x = X(i=42)
x.s = 'text'

x
# X(i=42, s='text')


x.s
# 'text'

asdict(x)
# {'i': 42, 's': 'text'}

Disclaimer: I am the creator (and maintener) of this library.

Share:
10,755

Related videos on Youtube

rominf
Author by

rominf

Updated on June 04, 2022

Comments

  • rominf
    rominf almost 2 years

    I'm writing a library to access REST API. It returns json with user object. I convert it to dict, and then convert it to dataclass object. The problem is that not all fields are fixed. I want to add additional fields (which are not specified in my dataclass) dynamically. I can simply assign values to my object, but they don't appear in the object representation and dataclasses.asdict function doesn't add them into resulting dict:

    from dataclasses import asdict, dataclass
    
    @dataclass
    class X:
        i: int
    
    x = X(i=42)
    x.s = 'text'
    
    x
    # X(i=42)
    
    x.s
    # 'text'
    
    asdict(x)
    # {'i': 42}