簡體   English   中英

從字典初始化 Python 數據類

[英]Initialize Python dataclass from dictionary

假設我想初始化下面的數據類

from dataclasses import dataclass

@dataclass
class Req:
    id: int
    description: str

我當然可以通過以下方式做到這一點:

data = make_request() # gives me a dict with id and description as well as some other keys.
                      # {"id": 123, "description": "hello", "data_a": "", ...}
req = Req(data["id"], data["description"])

但是,鑒於我需要的鍵始終是字典的子集,我是否可以通過字典解包來做到這一點?

req = Req(**data)  # TypeError: __init__() got an unexpected keyword argument 'data_a'

您可能會引入一個新函數,該函數將執行從 dict 到數據類的給定轉換:

import inspect
from dataclasses import dataclass

@dataclass
class Req:
    id: int
    description: str

def from_dict_to_dataclass(cls, data):
    return cls(
        **{
            key: (data[key] if val.default == val.empty else data.get(key, val.default))
            for key, val in inspect.signature(Req).parameters.items()
        }
    )

from_dict_to_dataclass(Req, {"id": 123, "description": "hello", "data_a": ""})
# Output: Req(id=123, description='hello')

請注意, if val.default == val.empty條件來檢查您的數據類是否設置了默認值。 如果它是真的,那么我們應該在構造數據類時考慮給定的值。

這是一個可以通用用於任何類的解決方案。 它只是過濾輸入字典以排除不是具有init==True類的字段名稱的鍵:

from dataclasses import dataclass, fields

@dataclass
class Req:
    id: int
    description: str

def classFromArgs(className, argDict):
    fieldSet = {f.name for f in fields(className) if f.init}
    filteredArgDict = {k : v for k, v in argDict.items() if k in fieldSet}
    return className(**filteredArgDict)

data = {"id": 123, "description": "hello", "data_a": ""}
req = classFromArgs(Req, data)
print(req)

輸出:

Req(id=123, description='hello')

更新:這是上述策略的一種變體,它創建了一個實用程序類,該類為使用它的每個數據類緩存dataclasses.fields (由@rv.kvetch 的評論提示,表達了對通過多次調用重復處理dataclasses.fields的性能問題相同的數據類)。

from dataclasses import dataclass, fields

class DataClassUnpack:
    classFieldCache = {}

    @classmethod
    def instantiate(cls, classToInstantiate, argDict):
        if classToInstantiate not in cls.classFieldCache:
            cls.classFieldCache[classToInstantiate] = {f.name for f in fields(classToInstantiate) if f.init}

        fieldSet = cls.classFieldCache[classToInstantiate]
        filteredArgDict = {k : v for k, v in argDict.items() if k in fieldSet}
        return classToInstantiate(**filteredArgDict)

@dataclass
class Req:
    id: int
    description: str
req = DataClassUnpack.instantiate(Req, {"id": 123, "description": "hello", "data_a": ""})
print(req)
req = DataClassUnpack.instantiate(Req, {"id": 456, "description": "goodbye", "data_a": "my", "data_b": "friend"})
print(req)

@dataclass
class Req2:
    id: int
    description: str
    data_a: str
req2 = DataClassUnpack.instantiate(Req2, {"id": 123, "description": "hello", "data_a": "world"})
print(req2)

print("\nHere's a peek at the internals of DataClassUnpack:")
print(DataClassUnpack.classFieldCache)

輸出:

Req(id=123, description='hello')
Req(id=456, description='goodbye')
Req2(id=123, description='hello', data_a='world')

Here's a peek at the internals of DataClassUnpack:
{<class '__main__.Req'>: {'description', 'id'}, <class '__main__.Req2'>: {'description', 'data_a', 'id'}}

解決此問題的方法是攔截數據類__init__並過濾掉無法識別的字段。

from dataclasses import dataclass, fields

@dataclass
class Req1:
    id: int
    description: str


@dataclass
class Req2:
    id: int
    description: str

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            if key in REQ2_FIELD_NAMES:
                setattr(self, key, value)

# To not re-evaluate the field names for each and every creation of Req2, list them here.
REQ2_FIELD_NAMES = {field.name for field in fields(Req2)}

data = {
    "id": 1,
    "description": "some",
    "data_a": None,
}

try:
    print("Call for Req1:", Req1(**data))
except Exception as error:
    print("Call for Req1:", error)

try:
    print("Call for Req2:", Req2(**data))
except Exception as error:
    print("Call for Req2:", error)

輸出:

Call for Req1: __init__() got an unexpected keyword argument 'data_a'
Call for Req2: Req2(id=1, description='some')

相關問題:

暫無
暫無

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

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