Answer a question

Let's say I want to initialize the below dataclass

from dataclasses import dataclass

@dataclass
class Req:
    id: int
    description: str

I can of course do it in the following way:

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"])

But, is it possible for me to do it with dictionary unpacking, given that the keys I need is always a subset of the dictionary?

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

Answers

Here's a solution that can be used generically for any class. It simply filters the input dictionary to exclude keys that aren't field names of the class with 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)

Output:

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

UPDATE: Here's a variation on the strategy above which creates a utility class that caches dataclasses.fields for each dataclass that uses it (prompted by a comment by @rv.kvetch expressing performance concerns around duplicate processing of dataclasses.fields by multiple invocations for the same dataclass).

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)

Output:

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'}}
Logo

Python社区为您提供最前沿的新闻资讯和知识内容

更多推荐