__post_init__ из python 3.x классов данных не вызывается при загрузке из yaml - PullRequest
0 голосов
/ 24 января 2020

Обратите внимание, что я уже упоминал вопрос StackOverflow здесь . Я отправляю этот вопрос, чтобы выяснить, безопасен ли вызов __post_init__ Пожалуйста, проверьте вопрос до конца.

Проверьте код ниже. На шаге 3 мы загружаем dataclass A из строки yaml. Обратите внимание, что он не вызывает метод __post_init__.

import dataclasses
import yaml


@dataclasses.dataclass
class A:
    a: int = 55

    def __post_init__(self):
        print("__post_init__ got called", self)


print("\n>>>>>>>>>>>> 1: create dataclass object")
a = A(33)
print(a)  # print dataclass
print(dataclasses.fields(a))

print("\n>>>>>>>>>>>> 2: dump to yaml")
s = yaml.dump(a)
print(s)  # print yaml repr

print("\n>>>>>>>>>>>> 3: create class from str")
a_ = yaml.load(s)
print(a_)  # print dataclass loaded from yaml str
print(dataclasses.fields(a_))

Решение, которое я вижу на данный момент, - это вызов __-post_init__ самостоятельно в конце, как в следующем фрагменте кода.

a_.__post_init__()

Я не уверен, является ли это безопасным воссозданием yaml сериализованного dataclass. Кроме того, это создаст проблему, когда __post_init__ получит kwargs, если поля dataclass имеют тип dataclasses.InitVar.

1 Ответ

0 голосов
/ 28 марта 2020

Это поведение работает как задумано. Вы сбрасываете существующий объект, поэтому при загрузке pyyaml ​​намеренно избегает инициализации объекта снова. Прямые атрибуты выгруженного объекта будут сохранены, даже если они созданы в __post_init__, потому что эта функция выполняется до сброса. Если вы хотите получить побочные эффекты __post_init__, такие как оператор печати в вашем примере, вам нужно убедиться, что инициализация происходит.

Есть несколько способов сделать это sh. Вы можете использовать метакласс или добавить подходы конструктора / представления, описанные в документации pyyaml ​​. Вы также можете вручную изменить выведенную строку в вашем примере так, чтобы она была '' !! python / object / new: 'вместо' '!! python / object:'. Если вашей конечной целью является создание файла yaml другим способом, то это может быть решением.

См. Ниже обновление вашего кода, который использует подход метакласса и вызывает __post_init__ при загрузке из сброшенный объект класса. Вызов cls(**fields) в from_yaml гарантирует, что объект инициализирован. yaml.load использует cls.__new__ для создания объектов, помеченных '' !! python / object: ', а затем загружает сохраненные атрибуты в объект вручную.

import dataclasses
import yaml


@dataclasses.dataclass
class A(yaml.YAMLObject):
    a: int = 55

    def __post_init__(self):
        print("__post_init__ got called", self)

    yaml_tag = '!A'
    yaml_loader = yaml.SafeLoader

    @classmethod
    def from_yaml(cls, loader, node):
        fields = loader.construct_mapping(node, deep=True)
        return cls(**fields)

print("\n>>>>>>>>>>>> 1: create dataclass object")
a = A(33)
print(a)  # print dataclass
print(dataclasses.fields(a))

print("\n>>>>>>>>>>>> 2: dump to yaml")
s = yaml.dump(a)
print(s)  # print yaml repr

print("\n>>>>>>>>>>>> 3: create class from str")
a_ = yaml.load(s, Loader=A.yaml_loader)
print(a_)  # print dataclass loaded from yaml str
print(dataclasses.fields(a_))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...