7

I've started making heavy use of the python dataclasses module and find it very useful. I especially like the flags that can be set on each field allowing for toggling of compare, init etc.

I often find however that there is a field which I wish to omit from the asdict behaviour of the class. In some situations this may be possible with the dict_factory argument, but it sometimes happens that a field will cause the asdict function to raise an exception before it is omitted through use of the dict_factory.

Can anyone else suggest a clean way to do this? Would it not be a useful additional flag to add to the dataclasses module?

Arne
  • 17,706
  • 5
  • 83
  • 99
kerzane
  • 375
  • 3
  • 8
  • 1
    Using `asdict` at all is a pretty iffy move, with the weird, inconsistent implicit copies and the fact that the behavior changes if you change other classes to use or not use `dataclass`. – user2357112 May 07 '20 at 06:01
  • Can you give a reason why you'd not want them included? – Arne May 07 '20 at 06:02

2 Answers2

1

You can add custom metadata to field like field(metadata={"include_in_dict":True}) and in the dict_factory you can check this before anything else and skip the field if needed.

if field_.metadata.get("include_in_dict", False):
    continue
Mateus Terra
  • 159
  • 1
  • 9
  • 1
    Thanks for this I'll give it a try. – kerzane Aug 20 '20 at 08:07
  • 3
    I may be missing something, but I cannot find a way to access the `fields()` object from inside the dict_factory, so I don't think this works. If I'm wrong, please update the answer with a more complete factory example. – JBCP May 24 '22 at 21:50
  • @JBCP It's not documented well, but `asdict(obj, dict_factory=df)` passes a list of name/value pairs constructed from the output of `dataclasses.fields(obj)`. (See https://github.com/python/cpython/blob/3.11/Lib/dataclasses.py#L1280). – chepner Apr 14 '23 at 17:12
1

I've ended up defining dict_factory in dataclass as staticmethod and then using in as_dict(). Found it more straightforward than messing with metadata.

from typing import Optional, Tuple
from dataclasses import asdict, dataclass

@dataclass
class Space:
    size: Optional[int] = None
    dtype: Optional[str] = None
    shape: Optional[Tuple[int]] = None

    @staticmethod
    def dict_factory(x):
        exclude_fields = ("shape", )
        return {k: v for (k, v) in x if ((v is not None) and (k not in exclude_fields))}


s1 = Space(size=2)
s1_dict = asdict(s1, dict_factory=Space.dict_factory)
print(s1_dict)
# {"size": 2}

s2 = Space(dtype='int', shape=(2, 5))
s2_dict = asdict(s2, dict_factory=Space.dict_factory)
print(s2_dict)
# {"dtype": "int"}
# no "shape" key, because it is excluded in dict_factory of the class.
Victor Di
  • 988
  • 10
  • 16