从外部Web服务,我收到一个如下所示的JSON响应(为方便起见,下面已经对其进行了反序列化):

alist = [
    {
        'type': 'type1',
        'name': 'dummy',
        'oid': 'some_id'
    },
    {
        'type': 'type2',
        'name': 'bigdummy',
        'anumber': 10
    }
]


对于每个type,都有一个类。我想要的是实例化各个类的对象,而无需使用一长串的if-elif

我尝试如下:

使用A type1定义类B(对于type2)和类from_dict(对于classmethod):

class A():
    def __init__(self, name, oid, optional_stuff=None):
        self.name = name
        self.oid = oid
        self.optional_stuff = optional_stuff

    @classmethod
    def from_dict(cls, d):
        name = d['name']
        oid = d['oid']
        optional_stuff = d.get('optional_stuff')

        return cls(name, oid, optional_stuff)

    def foo(self):
        print('i am class A')


class B():
    def __init__(self, name, anumber):
        self.name = name
        self.number = anumber

    @classmethod
    def from_dict(cls, d):
        name = d['name']
        anumber = d['anumber']

        return cls(name, anumber)


然后定义一个映射字典:

string_class_map = {
    'type1': A,
    'type2': B
}


最后将alist转换为from_dict函数可以轻松使用的东西:

alist2 = [
    {
        di['type']: {k: v for k, v in di.items() if k != 'type'}
    }
    for di in alist
]

[{'type1': {'name': 'dummy', 'oid': 'some_id'}},
 {'type2': {'name': 'bigdummy', 'anumber': 10}}]

object_list = [
    string_class_map[k].from_dict(v) for d in alist2 for k, v in d.items()
]


那给了我想要的输出;当我做:

a = object_list[0]
a.name


确实会打印'dummy'

问题是,是否有更好的方法从alist(我无法更改此输入)到object_list

最佳答案

只要参数名称完全匹配,就不需要from_dict类方法-尽管您可能仍希望通过它们来添加额外的错误处理。我们要做的就是使用参数解压缩。
首先,我将总结创建单个对象的过程。也就是说,单个“ from_dict” -y方法应处理类型的确定,准备其他参数的dict并调用类工厂。
从工厂创建这些类的基类似乎很有用-毕竟它们至少有一个共同点,就是可以用这种方式创建;您可以在该级别添加调试内容;这是工厂逻辑本身的便利位置。
您可以使用装饰器或元类来完成查找映射的创建,从而避免需要维护单独的数据块。


综上所述,我得到:

class JsonLoadable:
    _factory = {}


    def __str__(self):
        return f'{self.__class__.__name__}(**{{{self.__dict__}}})'


    @staticmethod # this is our decorator.
    def register(cls):
        # We use the class' __name__ attribute to get the lookup key.
        # So as long as the classes are named to match the JSON, this
        # automatically builds the correct mapping.
        JsonLoadable._factory[cls.__name__] = cls
        return cls


    @staticmethod
    def from_dict(d):
        d = d.copy()
        cls = JsonLoadable._factory[d.pop('type')]
        # this is the magic that lets us avoid class-specific logic.
        return cls(**d)


# I'm pretty sure there's a way to streamline this further with metaclasses,
# but I'm not up to figuring it out at the moment...
@JsonLoadable.register
class A(JsonLoadable):
    def __init__(self, name, oid, optional_stuff=None):
        self.name = name
        self.oid = oid
        self.optional_stuff = optional_stuff


@JsonLoadable.register
class B(JsonLoadable):
    def __init__(self, name, anumber):
        self.name = name
        self.number = anumber


# And now our usage is simple:
objects = [JsonLoadable.from_dict(d) for d in alist]

10-08 00:10
查看更多