My goal is to be able to merge multiple instances of the same attrs
class and skip 'None' values.
This allows me to create default values and later override them when I need to.
An example of the expected code-
from attr import attrs
@attrs(auto_attribs=True)
class A:
a: int = None
b: int = None
instance_1 = A(a=1, b=2)
instance_2 = A(b=3)
# Expected result
instance_3 = merge_instances(instance_1, instance_2)
# instance_3 = A(a=1, b=3)
The best solution I currently found was the following-
from attr import attrs
@attrs(auto_attribs=True)
class A:
a: int = None
b: int = None
def merge_instances(instance_1, instance_2):
dict_1 = instance_1.__dict__
dict_2 = instance_2.__dict__
new_values = {}
for key in dict_1:
new_values[key] = dict_2[key] if dict_2[key] is not None else dict_1[key]
return A(**new_values)
if __name__ == '__main__':
instance_1 = A(a=1, b=2)
instance_2 = A(b=3)
instance_3 = merge_instances(instance_1, instance_2)
print(instance_3)
Which kind of feels like a hack. If anyone has a simpler solution I'd love to hear it!
This is cleaner
def merge_instances(instance_1, instance_2):
dict_1 = instance_1.__dict__
dict_2 = instance_2.__dict__
new_values = {k: dict_2[k] or dict_1[k] for k in dict_1}
return A(**new_values)
Since this function is strictly related to class A
, you can make it a classmethod
from attr import attrs
@attrs(auto_attribs=True)
class A:
a: int = None
b: int = None
@classmethod
def merge_instances(cls, instance_1, instance_2):
dict_1 = instance_1.__dict__
dict_2 = instance_2.__dict__
return cls(
**{k: dict_2[k] or dict_1[k] for k in dict_1}
)
if __name__ == '__main__':
instance_1 = A(a=1, b=2)
instance_2 = A(b=3)
instance_3 = A.merge_instances(instance_1, instance_2)
Since you're already using attrs, this is more idiomatic:
from attrs import define, fields
@define
class A:
a: int = None
b: int = None
instance_1 = A(a=1, b=2)
instance_2 = A(b=3)
def merge_instances(i1: A, i2: A) -> A:
kw = {}
for f in fields(A):
v = getattr(i2, f.name)
kw[f.name] = v if v is not None else getattr(i1, f.name)
return A(**kw)
assert A(a=1, b=3) == merge_instances(instance_1, instance_2)
Another possibility that is one line longer, but feels cleaner to me is to use attrs.evolve()
:
def merge_instances(i1: A, i2: A) -> A:
kw = {}
for f in fields(A):
v = getattr(i2, f.name)
if v is not None:
kw[f.name] = v
return evolve(i1, **kw)
Even nicer if you get to use the walrus:
def merge_instances(i1: A, i2: A) -> A:
kw = {}
for f in fields(A):
if (v := getattr(i2, f.name)) is not None:
kw[f.name] = v
return evolve(i1, **kw)
which could be simplified even further:
def merge_instances(i1: A, i2: A) -> A:
return evolve(
i1,
**{
f.name: v
for f in fields(A)
if (v := getattr(i2, f.name)) is not None
},
)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.