Reputation: 3104
Let's assume you have defined a Python dataclass:
@dataclass
class Marker:
a: float
b: float = 1.0
What's the easiest way to copy the values from an instance marker_a
to another instance marker_b
?
Here's an example of what I try to achieve:
marker_a = Marker(1.0, 2.0)
marker_b = Marker(11.0, 12.0)
# now some magic happens which you hopefully can fill in
print(marker_b)
# result: Marker(a=1.0, b=2.0)
As a boundary condition, I do not want to create and assign a new instance to marker_b
.
OK, I could loop through all defined fields and copy the values one by one, but there has to be a simpler way, I guess.
Upvotes: 90
Views: 70724
Reputation: 192
You can define a custom copy function for your @dataclass
:
@dataclass
class Marker:
a: float
b: float = 1.0
def copy(self) -> 'Marker':
return Marker(self.a, self.b)
As @r.ook mentioned, you can make it a bit fancier as well:
@dataclass
class Marker:
a: float
b: float = 1.0
def copy(self) -> 'Marker':
return Marker(**self.__dict__)
Upvotes: 0
Reputation: 15800
Here's a version that also lets you choose the result dataclass type and override attributes:
dataclassWith(Y(x=2, z=5), y=3) # > Y(x=3, y=3, z=5)
dataclassWith(Y(x=2, z=5), X, x=99) # > X(z=5, x=99) # There is no z
MISSING = object()
def dataclassWith(other, clz=None, **kw):
if clz is None: clz = other.__class__
k = other.__dict__.copy()
k.update(kw)
return clz(**{k:v for k,v in k.items()
if getattr(clz, k, MISSING) is not MISSING})
class TestDataclassUtil(unittest.TestCase):
def test_dataclassWith(self):
@dataclasses.dataclass
class X():
x:int = 1
z:int = 99
@dataclasses.dataclass
class Y(X):
y:int = 2
r = dataclassWith(Y(x=2), y=3)
self.assertTrue(isinstance(r, Y))
self.assertTrue(r.x==2)
self.assertTrue(r.y==3)
self.assertTrue(r.z==99)
r = dataclassWith(Y(x=2), X, z=100)
self.assertTrue(isinstance(r, X))
self.assertTrue(r.x==2)
self.assertTrue(r.z==100)
Upvotes: 0
Reputation: 6923
The dataclasses.replace
function returns a new copy of the object.
Without passing in any changes, it will return a copy with no modification:
>>> import dataclasses
>>> @dataclasses.dataclass
... class Dummy:
... foo: int
... bar: int
...
>>> dummy = Dummy(1, 2)
>>> dummy_copy = dataclasses.replace(dummy)
>>> dummy_copy.foo = 5
>>> dummy
Dummy(foo=1, bar=2)
>>> dummy_copy
Dummy(foo=5, bar=2)
Note that this is a shallow copy.
If a copy is undesirable, I would probably go with the following:
for key, value in dataclasses.asdict(dummy).items():
setattr(some_obj, key, value)
Upvotes: 161
Reputation: 13898
@dataclass
class Marker:
a: float
b: float = 1.0
marker_a = Marker(0.5)
marker_b = Marker(**marker_a.__dict__)
marker_b
# Marker(a=0.5, b=1.0)
If you didn't want to create a new instance, try this:
marker_a = Marker(1.0, 2.0)
marker_b = Marker(11.0, 12.0)
marker_b.__dict__ = marker_a.__dict__.copy()
# result: Marker(a=1.0, b=2.0)
Not sure whether that's considered a bad hack though...
Upvotes: 10
Reputation: 896
Another option which may be more elegant:
import dataclasses
marker_a = Marker(1.0, 2.0)
marker_b = Marker(**dataclasses.asdict(marker_a))
Upvotes: 7
Reputation: 61042
I think that looping over the fields probably is the easiest way. All the other options I can think of involve creating a new object.
from dataclasses import fields
marker_a = Marker(5)
marker_b = Marker(0, 99)
for field in fields(Marker):
setattr(marker_b, field.name, getattr(marker_a, field.name))
print(marker_b) # Marker(a=5, b=1.0)
Upvotes: 12