How to achieve the reverse of "attr.asdict(MyObject)" using Python module 'attrs'
Asked Answered
I

4

17

In documentation of Python module attrs stated that there is a method to convert attributes’ class into dictionary representation:

Example:

>>> @attr.s
... class Coordinates(object):
...     x = attr.ib()
...     y = attr.ib()
...
>>> attr.asdict(Coordinates(x=1, y=2))
{'x': 1, 'y': 2}

How can I achieve the opposite, instantiating the Coordinates from its valid dictionary representation without boilerplate and with the joy of the attrs module?

Injured answered 28/6, 2017 at 12:5 Comment(0)
I
19

Apparently as easy as using dictionary unpacking (double star) operator in corresponding attrs class instantiation.

Example:

>>> Coordinates(**{'x': 1, 'y': 2})
Coordinates(x=1, y=2)
Injured answered 29/6, 2017 at 8:1 Comment(1)
This will work only if your class is flat and has no nested complex types.Emmaemmalee
T
11

As a more universal solution, which works with attrs nested classes, enums or any other type annotated structures you can use https://github.com/Tinche/cattrs. It also supports structure/unstructure customization by defining the structure/unstructure hooks

Example:

import attr, cattr
   
@attr.s(slots=True, frozen=True)  # It works with normal classes too.
class C:
        a = attr.ib()
        b = attr.ib()
    
instance = C(1, 'a')
cattr.unstructure(instance)
# {'a': 1, 'b': 'a'}
cattr.structure({'a': 1, 'b': 'a'}, C)
# C(a=1, b='a')
Termitarium answered 15/3, 2019 at 6:20 Comment(0)
I
3

I do this in my web app to be able to serialize/de-serialize into JSON:

First I made a method on my classes that returns a more serializing friendly version:

def asdict(self, serializable=True):
    if serializable:
        as_dict['type'] = self.__class__.__name__
        return as_dict
    else:
        return attr.asdict(self)

Then when I need to convert one of these dictionaries (JSON objects actually) back into a class instance:

obj_type = values.pop('type')
if obj_type in obj_list:
    obj = getattr(sys.modules[__name__], obj_type)(**values)
Intradermal answered 20/10, 2017 at 3:40 Comment(2)
As far as I understand, your solution requires an additional state that will carry the type name? Also, it is not clear to what classes do you refer to by saying: “I made a method on my classes.” Either way, I receive only python dictionaries I cannot modify but can expect concrete keys to model it into simple DTO for more comfort further usage.Injured
Also, @patrick-shechetin you can make a generic method for dynamic creation of your attrs DTOs from your JSON dictionaries. Even more, if you have a JSON schema for them you don’t need to carry a type name in payload itself. As long as you can ensure your dictionary validates against the schema of a concrete type. You can find more in my other Q/A regarding JSL and attrs.Injured
S
0

Modern versions of attrs library (I teseted with attrs-23.2.0) implement __getstate__ and __setstate__ functions for serialization with pickle. These functions return and accept the state dictionary.

Here is a little example unit-test for that:

def test_attrs():
    from attrs import define

    @define
    class Coordinates:
        x: int
        y: int

    c0 = Coordinates(x=1, y=0)
    c1 = Coordinates(x=0, y=1)
    assert c0 != c1

    d0 = c0.__getstate__()
    assert isinstance(d0, dict)
    c1.__setstate__(d0)
    assert c0 == c1

Smew answered 14/8 at 19:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.