Here is a solution for nested dataclasses.
The relevant underlying implementation is:
# from lib/python3.11/dataclasses.py:1280
def _asdict_inner(obj, dict_factory):
if _is_dataclass_instance(obj):
result = []
for f in fields(obj):
value = _asdict_inner(getattr(obj, f.name), dict_factory)
result.append((f.name, value))
return dict_factory(result)
# ... (other types below here)
As you can see, the relevant override for dataclasses is fields
. Fields calls _FIELDS
which refers to self.__dataclass_fields__
. Unfortunately this is not easy to overwrite since a lot of other dataclass functions rely on getting the "ground truth" of the base fields from this function.
When an implementation is tightly coupled like this, the easiest fix is just overwriting the library's interface with your desired behavior. Here is how I solved this, making a new override method __dict_factory_override__
(and filtering for dataclass values != 0):
src/classes.py
import math
from dataclasses import dataclass
@dataclass
class TwoLayersDeep:
f: int = 0
@dataclass
class OneLayerDeep:
c: TwoLayersDeep
d: float = 1.0
e: float = 0.0
def __dict_factory_override__(self):
normal_dict = {k: getattr(self, k) for k in self.__dataclass_fields__}
return {
k: v for k, v in normal_dict.items()
if not isinstance(v, float) or not math.isclose(v, 0)
}
@dataclass
class TopLevelClass:
a: OneLayerDeep
b: int = 0
src/dataclasses_override.py
import dataclasses
# If dataclass has __dict_factory_override__, use that instead of dict_factory
_asdict_inner_actual = dataclasses._asdict_inner
def _asdict_inner(obj, dict_factory):
if dataclasses._is_dataclass_instance(obj):
if getattr(obj, '__dict_factory_override__', None):
user_dict = obj.__dict_factory_override__()
for k, v in user_dict.items(): # in case of further nesting
if dataclasses._is_dataclass_instance(v):
user_dict[k] = _asdict_inner(v, dict_factory)
return user_dict
return _asdict_inner_actual(obj, dict_factory)
dataclasses._asdict_inner = _asdict_inner
asdict = dataclasses.asdict
main.py
from src.classes import OneLayerDeep, TopLevelClass, TwoLayersDeep
from src.dataclasses_override import asdict
print(asdict(TopLevelClass(a=OneLayerDeep(c=TwoLayersDeep()))))
# {'a': {'c': {'f': 0}, 'd': 1.0}, 'b': 0}
# As expected, e=0.0 is not printed as it isclose(0)
"id"
. So adding ato_dict()
method will produce anything you want. In what way did that not work? – OmnipotencemyClass.to_dict()
instead. – Omnipotence