Search code examples
pythonpython-dataclasses

How can I functionally update a dataclass?


I'd like to copy an instance of a frozen dataclass, changing just one field ("functional update").

Here's what I tried

from dataclasses import dataclass, asdict    
@dataclass(frozen = True)    
class Pos:    
    start: int    
    end: int    
     
def adjust_start(pos: Pos, delta: int) -> Pos:    
   # TypeError: type object got multiple values for keyword argument 'start'    
   return Pos(**asdict(pos), start = pos.start + delta)    
     
adjust_start(Pos(1, 2), 4)   

What I'm looking for:

  • Is there a more straightforward way than converting to/from dicts?
  • How to get around the TypeError: if there is a way to functionally update kwargs then that could work.

In Scala, a functional update of a case class (Scala dataclass) can be done like this: pos.copy(start = pos.start + delta).


Solution

  • dataclasses.replace() to the rescue.

    dataclasses.replace(obj, /, **changes) creates a new object of the same type as obj, replacing fields with values from changes.

    import dataclasses
    
    
    @dataclasses.dataclass(frozen=True)
    class Pos:
        start: int
        end: int
    
    
    def adjust_start(pos: Pos, delta: int) -> Pos:
        return dataclasses.replace(pos, start=pos.start + delta)
    
    
    p = adjust_start(Pos(1, 2), 4)
    

    Personally, I might put adjust on the dataclass itself:

    import dataclasses
    
    
    @dataclasses.dataclass(frozen=True)
    class Pos:
        start: int
        end: int
    
        def adjust(self, *, start: int, end: int) -> "Pos":
            return dataclasses.replace(
                self,
                start=self.start + start,
                end=self.end + end,
            )
    
    
    p = Pos(1, 2).adjust(start=4)