Search code examples
pythonpython-dataclasses

Create an updated repr that includes variables calculated in __post_init__ python dataclasses


I want to be able to override the __repr__ function in my dataclasses to include all class variables from self.dict, not just fields, but still omitting fields with repr=False, including in nested dataclasses.

Simplified version of my code:

from dataclasses import dataclass, field, fields
import utils

@dataclass
class Bar:
    _data: dict = field(repr=False)
    bar: int = 0

    def __post_init__(self):
        """more nested dataclasses"""

@dataclass
class Foo:
    _data: dict = field(repr=False)
    foo: int = 0

    def __post_init__(self):
        self.bar = Bar(**utils.clean(_data, 'bar'))

foo = Foo(**data)
print(foo)

What I want (this or something similar):

Foo(foo=0, bar=Bar(...)) but still keep the same formatting when asked print(foo.bar): Bar(bar=0)

What I get with the default repr (the code is functional so things like foo.bar.bar work as intended, I want the repr to display everything omitting fields where repr=False):

Foo(foo=0)

Solutions I've tried:

# works, kinda, but doesn't omit fields where repr=False
def __repr__(self):
    return f'{self.__class__.__qualname__}({self.__dict__!r})'

# modified solution from a similar question on stack overflow, but doesn't return !r formatting
# for every recursion depth when copied to every dataclass and I'm not sure how I would change it
# to format nested dataclasses with '...'
def dict_without_repr_field(self) -> dict:
    dict_to_show = self.__dict__.copy()
    for key in self.__dict__.keys():
        try:
        if not self.__dataclass_fields__[key].repr:
            dict_to_show.pop(key)
        except KeyError:
            pass
    return dict_to_show

def __repr__(self):
    updated_dict = self.dict_without_repr_field()
    return f'{self.__class__.__qualname__}({updated_dict!r})'

Any help would be appreciated, thanks!


Solution

  • You have to declare the type of bar and disable its initialisation.

    from dataclasses import field
    
    @dataclass
    class Foo:
        _data: dict = field(repr=False)
        foo: int = 0
        bar: Bar = field(init=False) # <-
    
        def __post_init__(self):
            self.bar = Bar(**utils.clean(_data, 'bar'))