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)
简单的 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)