Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend dataclass' __repr__ programmatically

Suppose I have a dataclass with a set method. How do I extend the repr method so that it also updates whenever the set method is called:

from dataclasses import dataclass
@dataclass
class State:
    A: int = 1
    B: int = 2
    def set(self, var, val):
        setattr(self, var, val)

Ex:

In [2]: x = State()

In [3]: x
Out[3]: State(A=1, B=2)

In [4]: x.set("C", 3)

In [5]: x
Out[5]: State(A=1, B=2)

In [6]: x.C
Out[6]: 3

The outcome I would like

In [7]: x
Out[7]: State(A=1, B=2, C=3)
like image 602
badbayesian Avatar asked Apr 30 '26 15:04

badbayesian


2 Answers

The dataclass decorator lets you quickly and easily build classes that have specific fields that are predetermined when you define the class. The way you're intending to use your class, however, doesn't match up very well with what dataclasses are good for. You want to be able to dynamically add new fields after the class already exists, and have them work with various methods (like __init__, __repr__ and presumably __eq__). That removes almost all of the benefits of using dataclass. You should instead just write your own class that does what you want it to do.

Here's a quick and dirty version:

class State:
    _defaults = {"A": 1, "B": 2}
    
    def __init__(self, **kwargs):
        self.__dict__.update(self._defaults)
        self.__dict__.update(kwargs)
        
    def __eq__(self, other):
        return self.__dict__ == other.__dict__ # you might want to add some type checking here
        
    def __repr__(self):
        kws = [f"{key}={value!r}" for key, value in self.__dict__.items()]
        return "{}({})".format(type(self).__name__, ", ".join(kws))

This is pretty similar to what you get from types.SimpleNamespace, so you might just be able to use that instead (it doesn't do default values though).

You could add your set method to this framework, though it seems to me like needless duplication of the builtin setattr function you're already using to implement it. If the caller needs to dynamically set an attribute, they can call setattr themselves. If the attribute name is constant, they can use normal attribute assignment syntax instead s.foo = "bar".

like image 186
Blckknght Avatar answered May 02 '26 04:05

Blckknght


I think the cleanest solution is to reuse the internals of dataclasses: which is pretty much mimicking a super().__repr__ for other use cases.

@dataclasses.dataclass
class Component:
    id: str
    categories: list[str] = dataclasses.field(default_factory=list)

    @property
    def url(self) -> str:
        """
        Component url in logbook
        """
        return f"http://example.com/components?id={self.id}"])

    def __repr__(self):
        """
        Inserting the logbook url into the repr after the 'id' field.
        """
        fields = dataclasses.fields(self)
        fields = [f for f in fields if f.repr]
        repr_fn = dataclasses._repr_fn(dataclasses.fields(self), {})
        parent_repr = repr_fn(self)
        # super().__repr__() would not work because it gives object.__repr__
        parts = parent_repr.split(",")  # Split the representation by commas
        additional_info = f", url={self.url}"  # Additional property information
        parts.insert(
            1, additional_info
        )  # Insert the additional info after the 'name' field
        return ", ".join(parts)  # Join the parts back together
like image 20
Daniel Böckenhoff Avatar answered May 02 '26 03:05

Daniel Böckenhoff



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!