簡體   English   中英

如何從字典列表中實例化來自不同類的對象?

[英]How to instantiate objects from different classes from a list of dictionaries?

從外部Web服務,我收到一個如下所示的JSON響應(為方便起見,下面已經對它進行了反序列化):

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

對於每種type都有一個類。 我想要的是實例化各個類的對象,而無需使用一長串if-elif

我嘗試如下:

使用from_dict classmethod定義類A (對於type1 )和類B (對於type2 ):

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]

與其為在alist中可能遇到的每個'type'編寫自定義類, alist使用允許您訪問其屬性的通用類(使用示例類所做的一切)會更簡單。

為此,下面的代碼定義了內置dict類的子類,該類將允許訪問它們中的值,就好像它們是實例屬性一樣。

這就是我的意思:

from collections import Iterable, Mapping
from pprint import pprint, pformat


class AttrDict(dict):
    def __init__(self, d):
        for k, v in d.items():
            if isinstance(v, Mapping):
                d[k] = AttrDict(v)
            elif isinstance(v, Iterable) and not isinstance(v, str):
                d[k] = [AttrDict(x) if isinstance(x, Mapping) else x
                            for x in v]
        self.__dict__.update(d)

    def __repr__(self):
        return 'AttrDict({})'.format(repr(self.__dict__))


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

object_list = [AttrDict(obj) for obj in alist]
pprint(object_list)
print()  # -> [AttrDict({'type': 'type1', 'name': 'dummy', 'oid': 'some_id'}),
         # ->  AttrDict({'type': 'type2', 'name': 'bigdummy', 'anumber': 10})]


a = object_list[0]
print(repr(a.name))  # -> 'dummy'

根據您對我的第一個答案的反饋,這是另一個-完全不同的-一個使您可以輕松創建任意多或少獨立類的方法。 我認為它比@Karl Knechtel的回答有所改進,因為不需要裝飾器並使用它來“注冊”每個子類-通過從公共基類派生每個子類來有效地自動進行。

基本上,這只是我在回答問題時使用的模式的改編:

錯誤使用__new__生成類?

class Base:
    class Unknown(Exception): pass

    @classmethod
    def _get_all_subclasses(cls):
        """ Recursive generator of all class' subclasses. """
        for subclass in cls.__subclasses__():
            yield subclass
            for subclass in subclass._get_all_subclasses():
                yield subclass

    def __new__(cls, d):
        """ Create instance of appropriate subclass using type id. """
        type_id = d['type']
        for subclass in cls._get_all_subclasses():
            if subclass.type_id == type_id:
                # Using "object" base class method avoids recursion here.
                return object.__new__(subclass)
        else:  # No subclass with matching type_id found.
            raise Base.Unknown(f'type: {type_id!r}')

    def __repr__(self):
        return f'<{self.__class__.__name__} instance>'


class A(Base):
    type_id = 'type1'

    def __init__(self, d):
        self.name = d['name']
        self.oid = d['oid']
        self.optional_stuff = d.get('optional_stuff')

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


class B(Base):
    type_id = 'type2'

    def __init__(self, d):
        self.name = d['name']
        self.anumber = d['anumber']


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


object_list = [Base(obj) for obj in alist]
print(f'object_list: {object_list}') # -> [<A instance>, <B instance>]
a = object_list[0]
print(repr(a.name))  # -> 'dummy'
b = object_list[1]
print(repr(b.name))  # -> 'bigdummy'

如果您使用的是Python 3.6+,則可以使用該版本中添加的object.__init_subclass__()類方法來實現更簡潔的實現:

class Base:
    _registry = {}

    @classmethod
    def __init_subclass__(cls, **kwargs):
        type_id = kwargs.pop('type_id', None)
        super().__init_subclass__(**kwargs)
        if type_id is not None:
            cls._registry[type_id] = cls

    def __new__(cls, d):
        """ Create instance of appropriate subclass. """
        type_id = d['type']
        subclass = Base._registry[type_id]
        return object.__new__(subclass)

    def __repr__(self):
        return f'<{self.__class__.__name__} instance>'


class A(Base, type_id='type1'):
    def __init__(self, d):
        self.name = d['name']
        self.oid = d['oid']
        self.optional_stuff = d.get('optional_stuff')

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


class B(Base, type_id='type2'):
    def __init__(self, d):
        self.name = d['name']
        self.anumber = d['anumber']


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


object_list = [Base(obj) for obj in alist]
print(f'object_list: {object_list}') # -> [<A instance>, <B instance>]
a = object_list[0]
print(repr(a.name))  # -> 'dummy'
b = object_list[1]
print(repr(b.name))  # -> 'bigdummy'

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM