3
\$\begingroup\$

I want to dump a dataclass instance into a dict by omitting some of the attrs. I used the metadata parameter of the field method. Also I want any bytes object to be turned into a hex string. What do you think?

def asdict(obj):
    """ use with dataclass only """
    dict_ = {}
    for k, v in obj.__dataclass_fields__.items():
        if v.metadata.get('is_exported') != False:
            dict_[k] = getattr(obj, k)
        if type(getattr(obj,k)) == bytes:
            dict_[k] = getattr(obj, k).hex()
    return dict_


@dataclass
class Password:
    password: str = field(metadata = {'is_exported' : False})

EDIT: I've finally been to the source code of python and tinkered with the dataclasses module and modified slightly the asdict method (the _adict_inner is wrapped by asdict):

def _asdict_inner(obj, dict_factory):
    obj_type = type(obj)
    if obj_type in _ATOMIC_TYPES:
        return obj
    elif hasattr(obj_type, _FIELDS):
        # dataclass instance: fast path for the common case
        if dict_factory is dict:
            return {
                f.name: _asdict_inner(getattr(obj, f.name), dict)
                for f in fields(obj) if f.metadata.get('is_exported') != False # here is the addon
            }
        else:
            return dict_factory([
                (f.name, _asdict_inner(getattr(obj, f.name), dict_factory))
                for f in fields(obj) if f.metadata.get('is_exported') != False # here is the addon
            ])

Done a few test and it works flawlessly so far. Now I have a custom dataclasses module that allows me to omit some attrs from the dict export function.

\$\endgroup\$
4
  • \$\begingroup\$ Have you read the documentation for the method that already does this? \$\endgroup\$
    – Reinderien
    Feb 25 at 21:23
  • \$\begingroup\$ sure, but it is not possible to exclude an attr on demand \$\endgroup\$
    – FTG
    Feb 25 at 21:25
  • \$\begingroup\$ Is this eventually exported to JSON, or something else? \$\endgroup\$
    – Reinderien
    Feb 25 at 22:33
  • \$\begingroup\$ It can but not mandatory \$\endgroup\$
    – FTG
    Feb 26 at 10:30

1 Answer 1

9
\$\begingroup\$

Don't type() == bytes; use isinstance. More broadly, though, bytes-to-hex conversion doesn't seem like it should be in this function; you can do another iteration on the dictionary in a separate function to accomplish this. If this is for export to JSON, the best place to do this is a custom encoder. If this isn't being done for JSON, I question why it's being done at all.

asdict is not a good name, since it conflicts with the stock implementation of dataclasses.asdict.

I think it's a good idea to explicitly fill in a True default for the metadata get() call.

Write unit tests.

Add PEP484 type hints (not that those can be entirely specific, here).

You should probably leverage the built-in fields instead of iterating on a dunder.

Suggested

from dataclasses import dataclass, field, fields
from typing import Any


def export(obj) -> dict[str, Any]:
    """ use with dataclass only """
    return {
        field.name: getattr(obj, field.name)
        for field in fields(obj)
        if field.metadata.get('is_exported', True)
    }


@dataclass
class Password:
    username: str = field(metadata={'is_exported': True})
    password: str = field(metadata={'is_exported': False})
    has_default: float


def test() -> None:
    actual = export(Password('bob', 'password', 3.2))
    expected = {
        'username': 'bob',
        'has_default': 3.2,
    }
    assert actual == expected


if __name__ == '__main__':
    test()
\$\endgroup\$
4
  • \$\begingroup\$ Nice! My idea was to overload the dict_factory built-in method, but your solution seems nice as well. What about overloading this method by adding a check of the metadata of the field? Then I can take advantage of the recursion as well (github.com/python/cpython/blob/…) \$\endgroup\$
    – FTG
    Feb 26 at 10:34
  • \$\begingroup\$ I'm not sure what you mean by overloading. In the strictest sense, Python does not have signature overloading. Perhaps you mean overriding. And as for recursion... I guess it's possible, but you haven't demonstrated realistic data structures that call for it, so I'll remain skeptical that that's a good idea. \$\endgroup\$
    – Reinderien
    Feb 26 at 13:54
  • \$\begingroup\$ You're right, sorry I am not native English speaker, I meant overriding. I will try and will get back to post my code \$\endgroup\$
    – FTG
    Feb 26 at 13:56
  • \$\begingroup\$ Add edit in my initial question with some code. \$\endgroup\$
    – FTG
    Feb 26 at 19:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.