Python: 检索任意字典路径并修改数据?

Python: retrieve arbitrary dictionary path and amend data?

简单的 Python 问题,但我正在摸索答案!

我有一个任意长度的字符串数组,叫做 path,像这样:

path = ['country', 'city', 'items']

我还有一个字典 data 和一个字符串 unwanted_property。我知道字典是任意深度的,并且一直是字典,除了 items 属性,它始终是一个数组。

[澄清:这个问题的重点是我不知道 path 的内容是什么。他们可以是任何东西。我也不知道字典会是什么样子。我需要按照路径指示沿着字典走下去,然后从那里删除不需要的属性,而事先不知道路径是什么样的,或者它会有多长。]

我想检索与 path 匹配的数据对象部分(如果有),然后从每个部分中删除 unwanted_property

所以在上面的例子中,我想检索:

data['country']['city']['items']

然后从数组中的每一项中删除 unwanted_property。我想修改原始数据,而不是副本。 (澄清:我的意思是,我想以原始字典结束,只是减去不需要的属性。)

如何在代码中执行此操作?

我已经做到了:

path = ['country', 'city', 'items']
data = {
    'country': {
        'city': {
            'items': [
                {
                    'name': '114th Street',
                    'unwanted_property': 'foo',
                },
                {
                    'name': '8th Avenue',
                    'unwanted_property': 'foo',
                },
            ]
        }
    }
}
for p in path:
    if p == 'items':
        data = [i for i in data[p]]
    else:
        data = data[p]
if isinstance(data, list):
    for d in data:
        del d['unwanted_property']
else:
    del data['unwanted_property']

问题是这不会修改原始数据。它还依赖于 items 始终是路径中的最后一个字符串,但情况可能并非总是如此。

澄清:我的意思是我想结束:

{
    'country': {
        'city': {
            'items': [
                {
                    'name': '114th Street'
                },
                {
                    'name': '8th Avenue'
                },
            ]
        }
    }
}

而我在 data 中可用的只有 [{'name': '114th Street'}, {'name': '8th Avenue'}]

我觉得我需要像 XPath 这样的字典。

def delKey(your_dict,path):
     if len(path) == 1:
         for item in your_dict:
            del item[path[0]]
         return 
     delKey(  your_dict[path[0]],path[1:])

data
{'country': {'city': {'items': [{'name': '114th Street', 'unwanted_property': 'foo'}, {'name': '8th Avenue', 'unwanted_property': 'foo'}]}}}
path
['country', 'city', 'items', 'unwanted_property']

delKey(data,path)

data
{'country': {'city': {'items': [{'name': '114th Street'}, {'name': '8th Avenue'}]}}}

您需要删除密钥 unwanted_property

names_list = []

def remove_key_from_items(data):
    for d in data:
        if d != 'items':
            remove_key_from_items(data[d])
        else:
            for item in data[d]:
                unwanted_prop = item.pop('unwanted_property', None)
                names_list.append(item)

这将删除密钥。如果键 unwanted_property 不存在,则返回第二个参数 None

编辑: 即使没有第二个参数,您也可以使用 pop。如果密钥不存在,它将引发 KeyError

编辑 2:更新为递归深入 data 字典,直到找到 items 键,然后弹出 unwanted_property 并附加到 names_list 列表中以获得所需的输出。

你可以试试这个:

path = ['country', 'city', 'items']
previous_data = data[path[0]]
previous_key = path[0]
for i in path:
    previous_data = previous_data[i]
    previous_key = i
    if isinstance(previous_data, list):
          for c, b in enumerate(previous_data):
              if "unwanted_property" in b:
                   del previous_data[c]["unwanted_property"]

current_dict = {}
previous_data_dict = {}
for i, a in enumerate(path):
    if i == 0:
        current_dict[a] = data[a]
        previous_data_dict = data[a]
    else:
        if a == previous_key:
            current_dict[a] = previous_data
        else:
            current_dict[a] = previous_data_dict[a]
            previous_data_dict = previous_data_dict[a]
data = current_dict

print(data)

输出:

{'country': {'city': {'items': [{'name': '114th Street'}, {'name': '8th Avenue'}]}}, 'items': [{'name': '114th Street'}, {'name': '8th Avenue'}], 'city': {'items': [{'name': '114th Street'}, {'name': '8th Avenue'}]}}

您正在覆盖原始 data 引用的问题。将您的处理代码更改为

temp = data
for p in path:
    temp = temp[p]
if isinstance(temp, list):
    for d in temp:
        del d['unwanted_property']
else:
    del temp['unwanted_property']

在此版本中,您将 temp 设置为指向 data 所指的同一对象。 temp 不是副本,因此您对其所做的任何更改都将在原始对象中可见。然后你沿着 temp 前进,而 data 仍然是对根字典的引用。当您找到您正在查找的路径时,通过 temp 所做的任何更改都将在 data.

中可见

我还删除了行 data = [i for i in data[p]]。它创建了一个您永远不需要的不必要的列表副本,因为您没有修改存储在列表中的引用,只是修改了引用的内容。

事实上 path 不是预先确定的(除了 items 将成为 list 这一事实之外)意味着您最终可能会得到 KeyError 如果字典中不存在该路径,则在第一个循环中。你可以优雅地处理它做一些更像:

try:
    temp = data
    for p in path:
        temp = temp[p]
except KeyError:
    print('Path {} not in data'.format(path))
else:
    if isinstance(temp, list):
        for d in temp:
            del d['unwanted_property']
    else:
        del temp['unwanted_property']

您面临的问题是您将 data 变量重新分配给了一个不需要的值。在 for 循环的主体中,您将 data 设置为树上的下一个级别,例如给定您的示例 data 将具有以下值(按顺序),最多当它离开 for 循环时:

data == {'country': {'city': {'items': [{'name': '114th Street', 'unwanted_property': 'foo',}, {'name': '8th Avenue', 'unwanted_property': 'foo',},]}}}

data == {'city': {'items': [{'name': '114th Street', 'unwanted_property': 'foo',}, {'name': '8th Avenue', 'unwanted_property': 'foo',},]}}

data == {'items': [{'name': '114th Street', 'unwanted_property': 'foo',}, {'name': '8th Avenue', 'unwanted_property': 'foo',},]}

data == [{'name': '114th Street', 'unwanted_property': 'foo',}, {'name': '8th Avenue', 'unwanted_property': 'foo',},]

然后,当您最后从字典中删除项目时,您会留下 data 作为这些字典的列表,因为您丢失了结构的较高部分。因此,如果您为数据制作备份参考,您可以获得正确的输出,例如:

path = ['country', 'city', 'items']
data = {
    'country': {
        'city': {
            'items': [
                {
                    'name': '114th Street',
                    'unwanted_property': 'foo',
                },
                {
                    'name': '8th Avenue',
                    'unwanted_property': 'foo',
                },
            ]
        }
    }
}

data_ref = data

for p in path:
    if p == 'items':
        data = [i for i in data[p]]
    else:
        data = data[p]
if isinstance(data, list):
    for d in data:
        del d['unwanted_property']
else:
    del data['unwanted_property']

data = data_ref

使用 operator.itemgetter 您可以编写一个函数 return 最终键的值。

import operator, functools
def compose(*functions):
    '''returns a callable composed of the functions

    compose(f, g, h, k) -> f(g(h(k())))
    '''
    def compose2(f, g):
        return lambda x: f(g(x))
    return functools.reduce(compose2, functions, lambda x: x)

get_items = compose(*[operator.itemgetter(key) for key in path[::-1]])

然后像这样使用它:

path = ['country', 'city', 'items']
unwanted_property = 'unwanted_property'

for thing in get_items(data):
    del thing[unwanted_property]

当然,如果路径包含不存在的键,它会抛出一个 KeyError - 你可能应该考虑到这一点:

path = ['country', 'foo', 'items']
get_items = compose(*[operator.itemgetter(key) for key in path[::-1]])
try:
    for thing in get_items(data):
        del thing[unwanted_property]
except KeyError as e:
    print('missing key:', e)