Иметь вложенный словарь.
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__
пуст?
__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))