从外部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]