Начнем с того, что примерно такую функцию merge
, вероятно, можно написать, учитывая ваши знания о fields
, на примере экземпляра z
, демонстрирующем ее недостатки.Но, учитывая, что в этой реализации инструменты dataclass
используются точно так, как они предназначены, это означает, что они довольно стабильны, поэтому, если это вообще возможно, вы захотите использовать это:
from dataclasses import asdict, dataclass, field, fields, MISSING
@dataclass
class A:
a: str
b: float = 5
c: list = field(default_factory=list)
def merge(base, add_on):
retain = {}
for f in fields(base):
val = getattr(base, f.name)
if val == f.default:
continue
if f.default_factory != MISSING:
if val == f.default_factory():
continue
retain[f.name] = val
kwargs = {**asdict(add_on), **retain}
return type(base)(**kwargs)
fill = A('1', 1, [1])
x = A('a')
y = A('a', 2, [3])
z = A('a', 5, [])
print(merge(x, fill)) # good: A(a='a', b=1, c=[1])
print(merge(y, fill)) # good: A(a='a', b=2, c=[3])
print(merge(z, fill)) # bad: A(a='a', b=1, c=[1])
Получение z
случай правильно будет включать некоторый вид взлома, я бы лично снова украсил класс данных:
from dataclasses import asdict, dataclass, field, fields
def mergeable(inst):
old_init = inst.__init__
def new_init(self, *args, **kwargs):
self.__customs = {f.name for f, _ in zip(fields(self), args)}
self.__customs |= kwargs.keys()
old_init(self, *args, **kwargs)
def merge(self, other):
retain = {n: v for n, v in asdict(self).items() if n in self.__customs}
kwargs = {**asdict(other), **retain}
return type(self)(**kwargs)
inst.__init__ = new_init
inst.merge = merge
return inst
@mergeable
@dataclass
class A:
a: str
b: float = 5
c: list = field(default_factory=list)
fill = A('1', 1, [1])
x = A('a')
y = A('a', 2, [3])
z = A('a', 5, [])
print(x.merge(fill)) # good: A(a='a', b=1, c=[1])
print(y.merge(fill)) # good: A(a='a', b=2, c=[3])
print(z.merge(fill)) # good: A(a='a', b=5, c=[])
Это очень вероятно, что некоторые трудно угадатьХотя эффекты, так что используйте на свой страх и риск.