Как узнать, имеет ли поле в классе данных значение по умолчанию или оно задано явно? - PullRequest
1 голос
/ 03 июня 2019

У меня есть dataclass, для которого я хотел бы узнать, было ли каждое поле установлено явно или оно было заполнено либо default, либо default_factory.

Я знаю, что могу получить все поля, используя dataclasses.fields(...), и это, вероятно, будет работать для полей, которые используют default, но нелегко для полей, использующих default_factory.

Моя конечная цель - объединить два экземпляра класса данных A и B . В то время как B должен перекрывать только поля A , где A использует значение по умолчанию.

Вариант использования - это объект конфигурации, который можно указать в нескольких местоположениях , и некоторые имеют более высокий приоритет, чем другие.

Редактировать: пример

from dataclasses import dataclass, field

def bar():
  return "bar"

@dataclass
class Configuration:
  foo: str = field(default_factory=bar)

conf1 = Configuration(
)

conf2 = Configuration(
  foo="foo"
)

conf3 = Configuration(
  foo="bar"
)

Я хотел бы обнаружить, что conf1.foo использует значение по умолчанию и conf2.foo & conf3.foo были явно установлены.

1 Ответ

1 голос
/ 05 июня 2019

Начнем с того, что примерно такую ​​функцию 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=[])

Это очень вероятно, что некоторые трудно угадатьХотя эффекты, так что используйте на свой страх и риск.

...