Python: 使用嵌套字典创建 class 的嵌套数据class

Python: Use nested dict to create class of nested dataclass

在基本情况下,可以轻松地将字典映射到参数。下面显示了基本示例。

def func1(x: int, y: int):
    return x+y

input = {
    "x": 1,
    "y": 2,
}

## This Works
sum = func1(**input)
# sum = 3

Python 是否提供任何其他类型的快捷方式来为嵌套 类 启用此类行为?

from dataclasses import dataclass

@dataclass
class X:
  x: int


@dataclass
class Y:
  y: int


def func2(x: X, y: Y):
    return x.x + y.y


input_2 = {
    "X": {
        "x": 1,
    },
    "Y": {
        "y": 1,
    },
}

sum = func2(**input_2)
# TypeError: func2() got an unexpected keyword argument 'X'

我试过其他方法。这是一个可行的示例,但不是很通用。

sum = func2(X(input_2[X][x]),Y(input_2[Y][y])

pydantic 也失败了

from pydantic import BaseModel

class X(BaseModel):
  x: int


class Y(BaseModel):
  y: int


def func2(x: X, y: Y):
    return x.x + y.y


input_2 = {
    "X": {
        "x": 1,
    },
    "Y": {
        "y": 1,
    },
}
sum = func2(**input_2)
@dataclass
class Math:
"""Collection of Configurations and Data Loading Utilities for PlayFab Churn Featurization"""
    x: X
    y: Y

    @classmethod
    def load(cls, config_json):
        return Math(
            x=X(**config_json['x']),
            y=Y(**config_json['y']),
        )

我想绕过构造函数,给自己另辟蹊径。 我仍然获得所有嵌套数据类的好处,保持与我的旧构造函数的向后兼容性,并且仍然不需要 init 方法。

我想创建一个包含 XY 的新 class,假设 C 可以满足您的情况

from pydantic import BaseModel

class X(BaseModel):
  x: int


class Y(BaseModel):
  y: int


class C(X, Y):
    pass

def func2(c: C):
    x = c.x
    y = c.y
    return x + y


input_2 = C(**{
    "x": 1,
    "y": 1,
})
sum = func2(input_2)
print(sum)

您可以使用装饰器将函数参数的每个 dict 参数转换为其注释类型,假设在这种情况下类型为 dataclassBaseModel

dataclass-wizard 的示例 - 它也应该支持 嵌套数据类 模型:

import functools

from dataclasses import dataclass, is_dataclass
from dataclass_wizard import fromdict


def transform_dict_to_obj(f):
    name_to_tp = {name: tp for name, tp in f.__annotations__.items()
                  if is_dataclass(tp)}

    @functools.wraps(f)
    def new_func(**kwargs):
        for name, tp in name_to_tp.items():
            if name in kwargs:
                kwargs[name] = fromdict(tp, kwargs[name])

        return f(**kwargs)

    return new_func


@dataclass
class X:
  x: int


@dataclass
class Y:
  y: int


@transform_dict_to_obj
def func2(*, x: X, y: Y) -> str:
    return x.x + y.y


input_2 = {
    "x": {
        "x": 1,
    },
    "y": {
        "y": 1,
    },
}

sum = func2(**input_2)

print('Sum:', sum)
assert sum == 2  # OK

类似地,pydantic:

import functools

from pydantic import BaseModel

class X(BaseModel):
  x: int


class Y(BaseModel):
  y: int


def transform_dict_to_obj(f):
    name_to_from_dict = {name: tp.parse_obj
                         for name, tp in f.__annotations__.items()
                         if issubclass(tp, BaseModel)}

    @functools.wraps(f)
    def new_func(**kwargs):
        for name, from_dict in name_to_from_dict.items():
            if name in kwargs:
                kwargs[name] = from_dict(kwargs[name])

        return f(**kwargs)

    return new_func


@transform_dict_to_obj
def func2(*, x: X, y: Y) -> str:
    return x.x + y.y


input_2 = {
    "x": {
        "x": 1,
    },
    "y": {
        "y": 1,
    },
}

sum = func2(**input_2)

print('Sum:', sum)
assert sum == 2  # OK

稍微优化一点的版本,不用每次在装饰器中使用for循环,只需要在运行中添加你需要的逻辑,然后生成新的函数使用dataclasses._create_fn() 或类似的:

from dataclasses import dataclass, is_dataclass, _create_fn
from dataclass_wizard import fromdict


def transform_dict_to_obj_optimized(f):
    args = []
    body_lines = []
    return_type = f.__annotations__.pop('return', None)

    for name, tp in f.__annotations__.items():
        type_name = tp.__qualname__
        args.append(name)

        if is_dataclass(tp):
            body_lines.append(f'if {name}:')
            body_lines.append(f' {name} = fromdict({type_name}, {name})')

    body_lines.append(f'return original_fn({",".join(args)})')

    return _create_fn(f.__name__, args, body_lines,
                      return_type=return_type,
                      locals={'original_fn': f},
                      globals=globals())


@dataclass
class X:
  x: int


@dataclass
class Y:
  y: int


@transform_dict_to_obj_optimized
def func2(x: X, y: Y) -> int:
    return x.x + y.y


input_2 = {
    "x": {
        "x": 1,
    },
    "y": {
        "y": 1,
    },
}

sum = func2(**input_2)

print('Sum:', sum)
assert sum == 2  # OK