BayerSe
BayerSe

Reputation: 1171

Replace attributes in Data Class objects

I'd like to replace the attributes of a dataclass instance, analogous to namedtuple._replace(), i.e. making an altered copy of the original object:

from dataclasses import dataclass
from collections import namedtuple

U = namedtuple("U", "x")

@dataclass
class V:
    x: int

u = U(x=1)
u_ = u._replace(x=-1)
v = V(x=1)

print(u)
print(u_)
print(v)

This returns:

U(x=1)
U(x=-1)
V(x=1)

How can I mimic this functionality in dataclass objects?

Upvotes: 37

Views: 37533

Answers (5)

Yeldos Balgabekov
Yeldos Balgabekov

Reputation: 49

Just using replace will have reference pointer to previous mutable objects, hence two instances of a dataclass will share a state

So try something like this:

@dataclasses.dataclass(frozen=True)
class MyDataClass:
    mutable_object: list
    val: int
    
    def copy(self, **changes):
        return dataclasses.replace(deepcopy(self), **changes)

data = MyDataClass([], 1)
data2 = data.copy(val=2)
assert data.mutable_object != data2.mutable_object

Upvotes: 1

wim
wim

Reputation: 363233

The dataclasses module has a helper function for field replacement on instances (docs)

from dataclasses import replace

Usage differs from collections.namedtuple, where the functionality was provided by a method on the generated type (Side note: namedtuple._replace is documented/public API, using an underscore on the name was called a "regret" by the author, see link at end of answer).

>>> from dataclasses import dataclass, replace
>>> @dataclass
... class V:
...     x: int
...     y: int
...     
>>> v = V(1, 2)
>>> v_ = replace(v, y=42)
>>> v
V(x=1, y=2)
>>> v_
V(x=1, y=42)

For more background of the design, see the PyCon 2018 talk - Dataclasses: The code generator to end all code generators. The replace API is discussed in depth, along with other design differences between namedtuple and dataclasses, and some performance comparisons are shown.

Upvotes: 59

adg08101
adg08101

Reputation: 1

@dataclass()
class Point:
    x: float = dataclasses.Field(repr=True, default=0.00, default_factory=float, init=True, hash=True, compare=True,
                                 metadata={'x_axis': "X Axis", 'ext_name': "Point X Axis"})
    y: float = dataclasses.Field(repr=True, default=0.00, default_factory=float, init=True, hash=True, compare=True,
                                 metadata={'y_axis': "Y Axis", 'ext_name': "Point Y Axis"})

Point1 = Point(13.5, 455.25)
Point2 = dataclasses.replace(Point1, y=255.25)

print(Point1, Point2)

Upvotes: -2

Florian Brucker
Florian Brucker

Reputation: 10365

I know the question is about dataclass, but if you're using attr.s instead then you can use attr.evolve instead of dataclasses.replace:

import attr

@attr.s(frozen=True)
class Foo:
    x = attr.ib()
    y = attr.ib()

foo = Foo(1, 2)
bar = attr.evolve(foo, y=3)

Upvotes: 0

ely
ely

Reputation: 77484

dataclass is just syntactic sugar for the automatic creation of a special __init__ method and a host of other "boilerplate" methods based on type-annotated attributes.

Once the class is created, it is like any other, and its attributes can be overwritten and instances can be copied, e.g.

import copy

v_ = copy.deepcopy(v)
v_.x = -1

Depending on what the attributes are, you may only require copy.copy.

Upvotes: -1

Related Questions