Питонический способ проверить, имеет ли поле класса данных значение по умолчанию

В последнее время я использую python 3.7 и искал способы использовать новые классы данных. В основном у меня был метод, который перебирает поля класса данных и проверяет, имеют ли они значение по умолчанию:

from dataclasses import fields, MISSING

@classmethod
def from_json(cls)
    datacls_fields = fields(cls)
    for field in datacls_fields:
        if  (field.default != MISSING):
            #...

Однако в официальной документации говорится:

Значение MISSING - это контрольный объект, используемый для определения того, предоставлены ли параметры default и default_factory. Этот дозорный используется, поскольку значение None по умолчанию является допустимым. Никакой код не должен напрямую использовать значение MISSING.

Кто-нибудь знает лучший / более питонический способ сделать это?


person addonis1990    schedule 03.12.2018    source источник
comment
Возможно, питонический вопрос может заключаться в следующем: зачем вам определять поля со значением по умолчанию?   -  person Mehdi Sadeghi    schedule 03.12.2018
comment
Я думаю, что предупреждение против использования MISSING просто говорит о том, что это не должно иметь смысла для вашего кода. Если в одном из ваших классов данных было поле со значением по умолчанию, например, MISSING, это могло вызвать всякого рода странное поведение. Использование его в подобных проверках не вызовет никаких проблем.   -  person Patrick Haugh    schedule 03.12.2018
comment
Я думаю, как говорит @PatrickHaugh, ваш код правильный и на самом деле не делает ничего опасного. Во всяком случае, чтобы сделать это более питоническим, я бы использовал is not MISSING и убрал круглые скобки вокруг условия.   -  person Jacobo de Vera    schedule 23.07.2019


Ответы (2)


Это определение MISSING в исходном коде Python в dataclasses.py:

# A sentinel object to detect if a parameter is supplied or not.  Use
# a class to give it a better repr.
class _MISSING_TYPE:
    pass
MISSING = _MISSING_TYPE()

Определение довольно ясное, его вариант использования - только проверить, предоставлен ли параметр или нет, и провести различие между значением None и неподтвержденным значением:

def my_func(a=MISSING):
    if a is not MISSING:
        # a value has been supplied, whatever his value

Так что вполне нормально использовать его в коде для сравнения значений. Сообщая Ни один код не должен напрямую использовать значение MISSING, они просто предупреждают нас, что эта переменная не используется специально (кроме сравнения) и не должна использоваться в коде, чтобы избежать неожиданного поведения.

Вам следует обновить свой код, чтобы использовать более питонический синтаксис is not MISSING:

from dataclasses import fields, MISSING

@classmethod
def from_json(cls)
    datacls_fields = fields(cls)
    for field in datacls_fields:
        if field.default is not MISSING:

person Charlesthk    schedule 25.08.2020

Я думаю, что питоническое решение основано на использовании магии __dict__ (документация: https://docs.python.org/3/library/stdtypes.html#object.__dict__).

class Foo:
def __init__(self, a=1, b=2, c=3):
    self.a = a
    self.b = b
    self.c = c

x = Foo()
y = Foo(a=5)
print(x.__dict__ == Foo().__dict__)  # True
print(y.__dict__ == Foo().__dict__)  # False

По сути, при использовании в экземпляре объекта он возвращает словарь пар «поле-значение» (ключ-значение).

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

Код может быть более понятным.

P.S. Это ответ на вопрос, все ли поля имеют значения по умолчанию. В случае, если вам нужна информация о том, какие именно поля не имеют значений по умолчанию, это мое предлагаемое решение (основанное на той же логике, что и выше, но вместо того, чтобы сравнивать словари на равенство, мы находим разницу между ними):

# Need the actual fields back?
x_dict = x.__dict__
y_dict = y.__dict__
default = Foo().__dict__
print(dict(set(x_dict.items()) - set(default.items())))
print(dict(set(y_dict.items()) - set(default.items())))

Вывод при использовании этого кода:

{}
{'a': 5}

См. Как узнать разницу между двумя словарями в Python?, чтобы узнать больше о том, как найти разницу между двумя словарями.

person waykiki    schedule 30.08.2020