Динамическое преобразование dict в тип класса данных и экземпляр обратно в dict приводит к пустому dict

Иметь вложенный словарь.

stock_price = {'lastPrice': 129.1,
    'open': 126.57,
    'close': 0,
    'intraDayHighLow': {'min': 126.4, 'max': 129.55, 'value': 129.1},
    'weekHighLow': {'min': 49.7, 'minDate': '26-Jun-2023', 
       'max': 142.9,
       'maxDate': '30-Apr-2024',
        'value': 129.1},
    }

... который преобразуется в класс данных следующим образом.

from dataclasses import dataclass
def create_dataclass_from_dict(data_dict: dict, class_name:str):

    fields = {}
    
    for key, value in data_dict.items():
        if isinstance(value, dict):
            nested_class_name = key.capitalize()
            nested_class = create_dataclass_from_dict(value, class_name=nested_class_name)
            fields[key] = nested_class
        else:
            fields[key] = value
    
    return dataclass(type(class_name, (), fields))

....но когда я вижу dict экземпляра, он показывает пустой словарь...

Stock = create_dataclass_from_dict(stock_price, 'Stock')

my_stock = Stock()
my_stock.weekHighLow.minDate # gives '26-Jun-2023' as intended. Want this!!

my_stock.__dict__ # gives {}

... и поэтому не могу преобразовать его обратно в словарь, используя следующий код:

import json
def to_dict(obj):
    return json.loads(json.dumps(obj, default=lambda o: o.__dict__))

to_dict(my_stock) # gives {}

Где я ошибаюсь? Почему .__dict__ пуст?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
70
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Создание класса данных, почему __dict__ пусто

type(class_name, (), fields)

Это создает класс, который имеет fields в качестве атрибутов класса. Они будут присутствовать в Stock.__dict__, но не в my_stock.__dict__ для любого экземпляра my_stock из Stock.

my_stock.weekHighLow.minDate 

Это работает, потому что, если атрибут не найден в экземпляре класса, он затем ищется в объекте класса.

Использование dataclass() с классом, созданным таким образом, фактически не приведет к созданию полей класса данных.

Например, dataclass("Stock", (), {"last_price": 129.1}) похоже на написание

@dataclass
class Stock:
    last_price = 129.1

но поле класса данных требует аннотации типа, например

@dataclass
class Stock:
    last_price: float = 129.1

который создаст поле с именем last_price со значением по умолчанию 129.1.

Чтобы создать класс данных динамически, используйте вместо этого dataclasses.make_dataclass.

В вашем случае вам придется заменить

fields = {}
# ...
fields[key] = nested_class
# ...
fields[key] = value
# ...
return dataclass(type(class_name, (), fields))

к

fields = []
# ...
fields.append((key, nested_class, field(default_factory=nested_class)))
# ...
fields.append((key, type(value), field(default=value)))
# ...
return make_dataclass(class_name, fields)

(используя dataclasses.field вместо field)

Преобразование экземпляра класса данных в словарь

Чтобы преобразовать экземпляр класса данных (при условии, что класс данных создан правильно) в словарь, вам следует использовать dataclasses.asdict, а не полагаться на атрибут __dict__.

Более подробно это объясняется в этом ответе.

В завершение вот исправленный код:

from dataclasses import make_dataclass, field, asdict

# the dict
stock_price = {'lastPrice': 129.1,
    'open': 126.57,
    'close': 0,
    'intraDayHighLow': {'min': 126.4, 'max': 129.55, 'value': 129.1},
    'weekHighLow': {'min': 49.7, 'minDate': '26-Jun-2023', 
       'max': 142.9,
       'maxDate': '30-Apr-2024',
        'value': 129.1},
    }

# dynamic dataclass constructor
def create_dataclass_from_dict(data_dict: dict, class_name:str):

    fields = []
    
    for key, value in data_dict.items():
        if isinstance(value, dict):
            nested_class_name = key.capitalize()
            nested_class = create_dataclass_from_dict(value, class_name=nested_class_name)
            fields.append((key, nested_class, field(default_factory=nested_class)))
        else:
            fields.append((key, type(value), field(default=value)))
    
    return make_dataclass(class_name, fields)

# instantiate
Stock = create_dataclass_from_dict(stock_price, 'Stock')
my_stock = Stock()

# check nested
print(my_stock.weekHighLow.minDate) # works '26-Jun-2023'

# Reconstruct to dict
print(asdict(my_stock))

Другие вопросы по теме