如何一般地序列化和反序列化字典中的对象

How to generically serialize and de-serialize objects from dictionaries

在编写一个小服务时,我发现我想从字典中构建 Python 对象,然后 "serialize" 将它们返回到字典中。

目前我的问题是混合使用 itemgettervars 的结果。没有给我预期的结果。更具体地说:

from operator import itemgetter

class Object(object):
    def __init__(self, foo: str, bar: int):
        self.foo, self.bar = foo, bar

    @staticmethod
    def from_dict(dictionary):
        attributes = ["foo", "bar"]
        return Object(*itemgetter(*attributes)(dictionary))

所以我们有这个名为 from_dict 的静态方法,它应该接受字典和 return 一个 Object 对象。

这是我想要的行为:

>> d = {"foo": "frobulate", "bar": 42}
>> obj = Object.from_dict(d)
>> vars(obj)
{"foo": "frobulate", "bar": 42}

然而,我得到的却是:

>> d = {"foo": "frobulate", "bar": 42}
>> obj = Object.from_dict(d)
>> vars(obj)
{"foo": ("frobulate",), "bar": (42,)}

其中属性设置为单个元素元组。

是否有一种干净的 python 方式来实现我期望的行为?

我正在努力避免重复,例如:

class Object(object):
    ...
    @staticmethod
    def from_dict(dictionary):
        return Object(foo=dictionary["foo"],bar=dictionary["bar"])

最终我想要一个通用方法,它接受 class cls 并且可以解析字典中的任何对象:

def parse_object_from_dict(cls, attributes, dictionary):
    return cls(*itemgetter(*attributes)(dictionary))

可以这样使用:

>> foo = parse_object_from_dict(Foo, ["a", "b"], {"a": 42, "b": 42})
>> obj = parse_object_from_dict(Object, ["foo", "bar"], {"foo": 42, "bar": 42}

并按预期设置属性(不是单个元素元组!)。

这可能吗?甚至是个好主意?

一种方法是使用 setattr

class Object(object):
    def __init__(self, foo=None, bar=None):
        self.foo = foo
        self.bar = bar

    @staticmethod
    def from_dict(dic):
        obj = Object()
        [setattr(obj, attr, dic[attr]) for attr in ["foo", "bar"]]
        return obj

另一种方法是使用 **,但可能是一种草率的做事方式,如果您的字典包含额外的键,可能会抛出错误。

class Object(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    @staticmethod
    def from_dict(dic):
        return Object(**dic)

data = {'foo': 'a', 'bar': 2}
o = Object.from_dict(data)

data = {'foo': 'a', 'bar': 2, 'baz': 3}
o = Object.from_dict(data) # this will throw an error due to the 'baz' key

最后一种方法(可能是最干净的方法)是尝试使用 Marshmallow,它将为您处理序列化和反序列化。

https://marshmallow.readthedocs.io/en/3.0/api_reference.html

对于您所描述的内容,您应该使用 class 方法。您要求输入字典具有与 __init__ 函数中的参数相同的键,但除此之外,您只是将 key-value 对推送到对象。

class Object:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    @classmethod
    def from_dict(cls, d):
        ob = cls(**d)
        for k,v in d.items():
            setattr(ob, str(k), v)    
        return ob