动态更新嵌套数据类值

Update nested dataclass values dynamically

我想知道我正在尝试做的事情是否可行,或者我的方法是否完全错误。

假设我们有 3 个 dataclasses:

@dataclass
class MyFirstClass:
    my_int: int = 1


@dataclass
class MySecondClass:
    first_class: MyFirstClass = field(default_factory=MyFirstClass)
    my_bool: bool = False


@dataclass
class MainClass:
    second_class: MySecondClass = field(default_factory=MySecondClass)
    my_str: str = 'Original String'

现在我想更新任何给定的值 class:

def test_this():
    # make copy of dataclass
    data_object = deepcopy(MainClass())

    # first print as is
    print(f'\n{asdict(data_object)}')

    # update first class
    data_object.second_class.first_class.my_int = 2

    # update second class
    data_object.second_class.my_bool = True

    # update main class
    data_object.my_str = 'Updated String'

    # print updated object
    print(f'\n{asdict(data_object)}')

这将输出:

Original:
{'second_class': {'first_class': {'my_int': 1}, 'my_bool': False}, 'my_str': 'Original String'}

Updated:
{'second_class': {'first_class': {'my_int': 2}, 'my_bool': True}, 'my_str': 'Updated String'}

我真的很喜欢做这样的事情的干净方法:

# make copy of dataclass
data_object = deepcopy(MainClass())

data_object.second_class.first_class.my_int = 2

与此相反:

data_object = replace(MainClass(), second_class=MySecondClass(first_class=MyFirstClass(my_int=2)))

基本上,我有一些测试想 运行 通过每个 dataclass 的每个字段的不同数据,所以我想知道是否有某种方法可以动态“获取”正确的要更新的字段?

我想到了最初循环遍历所创建对象的字段,如下所示:

def test_this():
    # make copy of dataclass
    data_object = deepcopy(MainClass())

    # loop through dataclass
    for field in fields(data_object):
        print(f'\nField: {field}')
        print(f'\nField Name: {field.name}')
        print(f'Field Type: {field.type}')
        print(f'Field Value: {getattr(data_object, field.name)}\n')

输出这个:

Field: Field(name='second_class',type=<class 'tests.sample_test_four.MySecondClass'>,default=<dataclasses._MISSING_TYPE object at 0x106943c10>,default_factory=<class 'tests.sample_test_four.MySecondClass'>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD)

Field Name: second_class
Field Type: <class 'tests.sample_test_four.MySecondClass'>
Field Value: MySecondClass(first_class=MyFirstClass(my_int=1), my_bool=False)


Field: Field(name='my_str',type=<class 'str'>,default='Original String',default_factory=<dataclasses._MISSING_TYPE object at 0x106943c10>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD)

Field Name: my_str
Field Type: <class 'str'>
Field Value: Original String

但是遍历 MainClass 对象仅将 second_classmy_str 显示为可用字段,而不是嵌套 classes 中的所有字段。

然后我想也许我可以将 dataclass 转换为 dict,然后从那里使用 deep_update,最后得到类似的东西:

使用pydantic

def test_this():
    # create initial dict
    data_object = asdict(deepcopy(MainClass()))

    # first print as is
    print(f'\nOriginal:\n{data_object}')

    # update dict
    updated_dict = deep_update(data_object, {'my_int': 2})

    # print updated
    print(f'\nUpdated:\n{updated_dict}')

或者创建我自己的更新函数:

Update value of a nested dictionary of varying depth

def update(d, u):
    for k, v in u.items():
        if isinstance(v, collections.abc.Mapping):
            d[k] = update(d.get(k, {}), v)
        else:
            d[k] = v
    return d

两者都输出:

Original:
{'second_class': {'first_class': {'my_int': 1}, 'my_bool': False}, 'my_str': 'Original String'}

Updated:
{'second_class': {'first_class': {'my_int': 1}, 'my_bool': False}, 'my_str': 'Original String', 'my_int': 2}

问题是它没有更新嵌套字段,而是在最外层添加了一个新字段 dict

此外,使用这种方法,我担心如果 class 中的任何一个具有相同名称的字段,那么哪个会被更新?

所以我不确定我正在尝试做的事情是否可能与获得预期结果所涉及的努力相比,或者我是否应该采用不同的方法?也许使用 JSON 文件而不是 dataclasses 并从那里做一些事情?

我改变了我的方法,我没有使用 dataclasses 来管理这个特定用例的数据注入。

因为无论如何我都会立即将 dataclass 转换为 JSON 对象,所以我想我只需要使用 JSON 文件来存储和注入我需要的数据并更新 dict相应地。

原始答案归功于:@Gustavo Alves Casqueiro

函数如下:

def update(dictionary: dict[str, any], key: str, value: any, nested_dict_name: str = None) -> dict[str, any]:
    if not nested_dict_name:  # if current (outermost) dict should be updated
        if key in dictionary.keys():  # check if key exists in current dict
            dictionary[key] = value
            return dictionary
    else:  # if nested dict should be updated
        if nested_dict_name in dictionary.keys():  # check if dict is in next layer
            if isinstance(dictionary[nested_dict_name], dict):
                if key in dictionary[nested_dict_name].keys():  # check if key exists in current dict
                    dictionary[nested_dict_name][key] = value
                    return dictionary
            if isinstance(dictionary[nested_dict_name], list):
                list_index = random.choice(range(len(dictionary[nested_dict_name])))  # pick a random dict from the list

                if key in dictionary[nested_dict_name][list_index].keys():  # check if key exists in current dict
                    dictionary[nested_dict_name][list_index][key] = value
                    return dictionary

    dic_aux = []

    # this would only run IF the above if-statement was not able to identity and update a dict
    for val_aux in dictionary.values():
        if isinstance(val_aux, dict):
            dic_aux.append(val_aux)

    # call the update function again for recursion
    for i in dic_aux:
        return update(dictionary=i, key=key, value=value, nested_dict_name=nested_dict_name)

完整的解决方案可以在这里找到:Update value of a nested dictionary of varying depth

在这里我找到了许多可用的选项来完成我需要做的事情。

老实说,我更愿意使用可以为我完成繁重工作的库,但我就是找不到能满足我需要的东西。