压平嵌套字典,压缩键

假设你有一本这样的字典:

{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}

你会如何把它平摊成这样:

{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
255119 次浏览

基本上与平铺嵌套列表的方法相同,您只需要做额外的工作,按键/值迭代字典,为新字典创建新键,并在最后一步创建字典。

import collections


def flatten(d, parent_key='', sep='_'):
items = []
for k, v in d.items():
new_key = parent_key + sep + k if parent_key else k
if isinstance(v, collections.MutableMapping):
items.extend(flatten(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)


>>> flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

对于Python >= 3.3,将导入更改为from collections.abc import MutableMapping以避免deprecation警告,并将collections.MutableMapping更改为MutableMapping

代码:

test = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}


def parse_dict(init, lkey=''):
ret = {}
for rkey,val in init.items():
key = lkey+rkey
if isinstance(val, dict):
ret.update(parse_dict(val, key+'_'))
else:
ret[key] = val
return ret


print(parse_dict(test,''))

结果:

$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

我使用python3.2,更新为您的python版本。

使用发电机:

def flat_dic_helper(prepand,d):
if len(prepand) > 0:
prepand = prepand + "_"
for k in d:
i = d[k]
if isinstance(i, dict):
r = flat_dic_helper(prepand + k,i)
for j in r:
yield j
else:
yield (prepand + k,i)


def flat_dic(d):
return dict(flat_dic_helper("",d))


d = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
print(flat_dic(d))




>> {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

原始海报需要考虑两大因素:

  1. 是否存在敲击键盘的问题?例如,{'a_b':{'c':1}, 'a':{'b_c':2}}将导致{'a_b_c':???}。下面的解决方案通过返回一个pair的可迭代对象来避免这个问题。
  2. 如果性能是一个问题,那么键减速器功能(我在此将其称为“join”)是否需要访问整个键路径,或者它可以只在树中的每个节点上执行O(1)工作?如果你希望能够说joinedKey = '_'.join(*keys),这将花费你O(N²)的运行时间。然而,如果你愿意说nextKey = previousKey+'_'+thisKey,那就得到O(N)时间。下面的解决方案允许您同时执行这两项操作(因为您只需将所有键连接起来,然后对它们进行后处理)。

(性能不太可能是问题,但我将详细说明第二点,以防其他人关心:在实现此功能时,存在许多危险的选择。如果你递归地执行这个操作,并且yield和reyield,或者任何东西等价的操作,它多次接触节点(这很容易意外发生),你正在做潜在的O(N²)工作,而不是O(N)。这是因为也许你正在计算一个键a然后a_1然后a_1_i…,然后计算a然后a_1然后a_1_ii…,但实际上你不应该再次计算a_1。即使你没有重新计算它,重新生成它(一种“逐级”的方法)也一样糟糕。一个很好的例子是考虑{1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}}的性能)

下面是我写的flattenDict(d, join=..., lift=...)函数,它可以适应许多目的,可以做任何你想做的事情。遗憾的是,要在不产生上述性能损失的情况下创建这个函数的惰性版本是相当困难的(许多python内置程序,如chain.from_iterable实际上并不是有效的,这是我在确定这个代码之前对三个不同版本的代码进行了大量测试后才意识到的)。

from collections import Mapping
from itertools import chain
from operator import add


_FLAG_FIRST = object()


def flattenDict(d, join=add, lift=lambda x:(x,)):
results = []
def visit(subdict, results, partialKey):
for k,v in subdict.items():
newKey = lift(k) if partialKey==_FLAG_FIRST else join(partialKey,lift(k))
if isinstance(v,Mapping):
visit(v, results, newKey)
else:
results.append((newKey,v))
visit(d, results, _FLAG_FIRST)
return results

为了更好地理解发生了什么,下面是一个图表,供那些不熟悉reduce(左)的人使用,也称为“;折叠左”。有时用一个初始值来代替k0(不是列表的一部分,传递给函数)。这里,J是我们的join函数。我们用lift(k)预处理每个kn

               [k0,k1,...,kN].foldleft(J)
/    \
...    kN
/
J(k0,J(k1,J(k2,k3)))
/  \
/    \
J(J(k0,k1),k2)   k3
/   \
/     \
J(k0,k1)    k2
/  \
/    \
k0     k1

这实际上与functools.reduce相同,但我们的函数对树的所有键路径执行此操作。

>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)

演示(否则我将放在docstring中):

>>> testData = {
'a':1,
'b':2,
'c':{
'aa':11,
'bb':22,
'cc':{
'aaa':111
}
}
}
from pprint import pprint as pp


>>> pp(dict( flattenDict(testData) ))
{('a',): 1,
('b',): 2,
('c', 'aa'): 11,
('c', 'bb'): 22,
('c', 'cc', 'aaa'): 111}


>>> pp(dict( flattenDict(testData, join=lambda a,b:a+'_'+b, lift=lambda x:x) ))
{'a': 1, 'b': 2, 'c_aa': 11, 'c_bb': 22, 'c_cc_aaa': 111}


>>> pp(dict( (v,k) for k,v in flattenDict(testData, lift=hash, join=lambda a,b:hash((a,b))) ))
{1: 12416037344,
2: 12544037731,
11: 5470935132935744593,
22: 4885734186131977315,
111: 3461911260025554326}

性能:

from functools import reduce
def makeEvilDict(n):
return reduce(lambda acc,x:{x:acc}, [{i:0 for i in range(n)}]+range(n))


import timeit
def time(runnable):
t0 = timeit.default_timer()
_ = runnable()
t1 = timeit.default_timer()
print('took {:.2f} seconds'.format(t1-t0))


>>> pp(makeEvilDict(8))
{7: {6: {5: {4: {3: {2: {1: {0: {0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0}}}}}}}}}


import sys
sys.setrecursionlimit(1000000)


forget = lambda a,b:''


>>> time(lambda: dict(flattenDict(makeEvilDict(10000), join=forget)) )
took 0.10 seconds
>>> time(lambda: dict(flattenDict(makeEvilDict(100000), join=forget)) )
[1]    12569 segmentation fault  python

... 唉,别以为那是我的错……


[由于中庸问题,不重要的历史注释]

关于的所谓重复,扁平化列表的字典(2层深度)

这个问题的解决方案可以通过执行sorted( sum(flatten(...),[]) )来实现。相反是不可能的:虽然flatten(...)可以通过映射一个高阶累加器从所谓的副本中恢复,但不能恢复键。(编辑:事实证明,所谓的重复所有者的问题完全不同,因为它只处理2级深度的字典,尽管该页上的一个答案给出了一个通用的解决方案。)

这并不局限于字典,而是实现.items()的每个映射类型。进一步列表更快,因为它避免了if条件。尽管如此,功劳还是归于伊姆兰:

def flatten(d, parent_key=''):
items = []
for k, v in d.items():
try:
items.extend(flatten(v, '%s%s_' % (parent_key, k)).items())
except AttributeError:
items.append(('%s%s' % (parent_key, k), v))
return dict(items)

这与imran和ralu的回答相似。它不使用生成器,而是使用闭包的递归:

def flatten_dict(d, separator='_'):
final = {}
def _flatten_dict(obj, parent_keys=[]):
for k, v in obj.iteritems():
if isinstance(v, dict):
_flatten_dict(v, parent_keys + [k])
else:
key = separator.join(parent_keys + [k])
final[key] = v
_flatten_dict(d)
return final


>>> print flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

上面的答案真的很管用。我只是想加上我写的unflatten函数:

def unflatten(d):
ud = {}
for k, v in d.items():
context = ud
for sub_key in k.split('_')[:-1]:
if sub_key not in context:
context[sub_key] = {}
context = context[sub_key]
context[k.split('_')[-1]] = v
return ud

注意:这并没有解释键中已经存在的'_',就像扁平化的对应物一样。

这是一种“功能性的”、“单行程序”实现。它是递归的,基于条件表达式和字典理解。

def flatten_dict(dd, separator='_', prefix=''):
return { prefix + separator + k if prefix else k : v
for kk, vv in dd.items()
for k, v in flatten_dict(vv, separator, kk).items()
} if isinstance(dd, dict) else { prefix : dd }

测试:

In [2]: flatten_dict({'abc':123, 'hgf':{'gh':432, 'yu':433}, 'gfd':902, 'xzxzxz':{"432":{'0b0b0b':231}, "43234":1321}}, '.')
Out[2]:
{'abc': 123,
'gfd': 902,
'hgf.gh': 432,
'hgf.yu': 433,
'xzxzxz.432.0b0b0b': 231,
'xzxzxz.43234': 1321}

Davoud的解决方案非常好,但当嵌套的字典也包含字典列表时,并不能给出令人满意的结果,但他的代码可以适应这种情况:

def flatten_dict(d):
items = []
for k, v in d.items():
try:
if (type(v)==type([])):
for l in v: items.extend(flatten_dict(l).items())
else:
items.extend(flatten_dict(v).items())
except AttributeError:
items.append((k, v))
return dict(items)

Python3.5中的功能和性能解决方案如何?

from functools import reduce




def _reducer(items, key, val, pref):
if isinstance(val, dict):
return {**items, **flatten(val, pref + key)}
else:
return {**items, pref + key: val}


def flatten(d, pref=''):
return(reduce(
lambda new_d, kv: _reducer(new_d, *kv, pref),
d.items(),
{}
))

这是更有表现力的:

def flatten(d, pref=''):
return(reduce(
lambda new_d, kv: \
isinstance(kv[1], dict) and \
{**new_d, **flatten(kv[1], pref + kv[0])} or \
{**new_d, pref + kv[0]: kv[1]},
d.items(),
{}
))

在使用:

my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}


print(flatten(my_obj))
# {'d': [1, 2, 3], 'cby': 10, 'cbx': 5, 'ca': 2, 'a': 1}

或者如果你已经在使用pandas,你可以像这样使用json_normalize():

import pandas as pd


d = {'a': 1,
'c': {'a': 2, 'b': {'x': 5, 'y' : 10}},
'd': [1, 2, 3]}


df = pd.json_normalize(d, sep='_')


print(df.to_dict(orient='records')[0])

输出:

{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}

这里有一个优雅的、就地替换的算法。使用Python 2.7和Python 3.5进行测试。使用点字符作为分隔符。

def flatten_json(json):
if type(json) == dict:
for k, v in list(json.items()):
if type(v) == dict:
flatten_json(v)
json.pop(k)
for k2, v2 in v.items():
json[k+"."+k2] = v2

例子:

d = {'a': {'b': 'c'}}
flatten_json(d)
print(d)
unflatten_json(d)
print(d)

输出:

{'a.b': 'c'}
{'a': {'b': 'c'}}

我发布了这段代码在这里以及匹配的unflatten_json函数。

使用生成器的Python 3.3解决方案:

def flattenit(pyobj, keystring=''):
if type(pyobj) is dict:
if (type(pyobj) is dict):
keystring = keystring + "_" if keystring else keystring
for k in pyobj:
yield from flattenit(pyobj[k], keystring + k)
elif (type(pyobj) is list):
for lelm in pyobj:
yield from flatten(lelm, keystring)
else:
yield keystring, pyobj


my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}


#your flattened dictionary object
flattened={k:v for k,v in flattenit(my_obj)}
print(flattened)


# result: {'c_b_y': 10, 'd': [1, 2, 3], 'c_a': 2, 'a': 1, 'c_b_x': 5}

简单的函数来平嵌套字典。对于Python 3,将.iteritems()替换为.items()

def flatten_dict(init_dict):
res_dict = {}
if type(init_dict) is not dict:
return res_dict


for k, v in init_dict.iteritems():
if type(v) == dict:
res_dict.update(flatten_dict(v))
else:
res_dict[k] = v


return res_dict
这个想法/要求是: 获得不保留父键的平面字典

用法示例:

dd = {'a': 3,
'b': {'c': 4, 'd': 5},
'e': {'f':
{'g': 1, 'h': 2}
},
'i': 9,
}


flatten_dict(dd)


>> {'a': 3, 'c': 4, 'd': 5, 'g': 1, 'h': 2, 'i': 9}

保留父密钥也很简单。

如果你使用的是pandas,那么在pandas.io.json._normalize1中有一个隐藏的函数叫做nested_to_record,它就是这样做的。

from pandas.io.json._normalize import nested_to_record


flat = nested_to_record(my_dict, sep='_')

在pandas版本中,0.24.x和更老的版本使用pandas.io.json.normalize(不使用_)

使用dict.popitem()在直接的嵌套列表类递归中:

def flatten(d):
if d == {}:
return d
else:
k,v = d.popitem()
if (dict != type(v)):
return {k:v, **flatten(d)}
else:
flat_kv = flatten(v)
for k1 in list(flat_kv.keys()):
flat_kv[k + '_' + k1] = flat_kv[k1]
del flat_kv[k1]
return {**flat_kv, **flatten(d)}

如果你想要平嵌套的字典,并想要所有唯一的键列表,那么这里是解决方案:

def flat_dict_return_unique_key(data, unique_keys=set()):
if isinstance(data, dict):
[unique_keys.add(i) for i in data.keys()]
for each_v in data.values():
if isinstance(each_v, dict):
flat_dict_return_unique_key(each_v, unique_keys)
return list(set(unique_keys))
def flatten(unflattened_dict, separator='_'):
flattened_dict = {}


for k, v in unflattened_dict.items():
if isinstance(v, dict):
sub_flattened_dict = flatten(v, separator)
for k2, v2 in sub_flattened_dict.items():
flattened_dict[k + separator + k2] = v2
else:
flattened_dict[k] = v


return flattened_dict

我总是更喜欢通过.items()访问dict对象,所以为了平摊字典,我使用下面的递归生成器flat_items(d)。如果你想再次使用dict,只需像这样包装它:flat = dict(flat_items(d))

def flat_items(d, key_separator='.'):
"""
Flattens the dictionary containing other dictionaries like here: https://stackoverflow.com/questions/6027558/flatten-nested-python-dictionaries-compressing-keys


>>> example = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
>>> flat = dict(flat_items(example, key_separator='_'))
>>> assert flat['c_b_y'] == 10
"""
for k, v in d.items():
if type(v) is dict:
for k1, v1 in flat_items(v, key_separator=key_separator):
yield key_separator.join((k, k1)), v1
else:
yield k, v

实际上,我最近写了一个名为cherrypicker的包来处理这种确切的事情,因为我必须经常这样做!

我认为下面的代码会给你你想要的东西:

from cherrypicker import CherryPicker


dct = {
'a': 1,
'c': {
'a': 2,
'b': {
'x': 5,
'y' : 10
}
},
'd': [1, 2, 3]
}


picker = CherryPicker(dct)
picker.flatten().get()

您可以使用以下方法安装软件包:

pip install cherrypicker

...在https://cherrypicker.readthedocs.io有更多的文档和指导。

其他方法可能更快,但这个包的优先级是使这些任务容易。如果你确实有一个很大的对象列表要扁平化,你也可以告诉CherryPicker使用并行处理来加快速度。

使用max_level和自定义reducer的压平嵌套字典,压缩键的变体。

  def flatten(d, max_level=None, reducer='tuple'):
if reducer == 'tuple':
reducer_seed = tuple()
reducer_func = lambda x, y: (*x, y)
else:
raise ValueError(f'Unknown reducer: {reducer}')


def impl(d, pref, level):
return reduce(
lambda new_d, kv:
(max_level is None or level < max_level)
and isinstance(kv[1], dict)
and {**new_d, **impl(kv[1], reducer_func(pref, kv[0]), level + 1)}
or {**new_d, reducer_func(pref, kv[0]): kv[1]},
d.items(),
{}
)


return impl(d, reducer_seed, 0)
def flatten_nested_dict(_dict, _str=''):
'''
recursive function to flatten a nested dictionary json
'''
ret_dict = {}
for k, v in _dict.items():
if isinstance(v, dict):
ret_dict.update(flatten_nested_dict(v, _str = '_'.join([_str, k]).strip('_')))
elif isinstance(v, list):
for index, item in enumerate(v):
if isinstance(item, dict):
ret_dict.update(flatten_nested_dict(item,  _str= '_'.join([_str, k, str(index)]).strip('_')))
else:
ret_dict['_'.join([_str, k, str(index)]).strip('_')] = item
else:
ret_dict['_'.join([_str, k]).strip('_')] = v
return ret_dict

我正在考虑UserDict的一个子类来自动神奇地平键。

class FlatDict(UserDict):
def __init__(self, *args, separator='.', **kwargs):
self.separator = separator
super().__init__(*args, **kwargs)


def __setitem__(self, key, value):
if isinstance(value, dict):
for k1, v1 in FlatDict(value, separator=self.separator).items():
super().__setitem__(f"{key}{self.separator}{k1}", v1)
else:
super().__setitem__(key, value)
< p >‌ 优点是键可以动态添加,或使用标准字典实例化,没有意外:

>>> fd = FlatDict(
...    {
...        'person': {
...            'sexe': 'male',
...            'name': {
...                'first': 'jacques',
...                'last': 'dupond'
...            }
...        }
...    }
... )
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond'}
>>> fd['person'] = {'name': {'nickname': 'Bob'}}
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob'}
>>> fd['person.name'] = {'civility': 'Dr'}
>>> fd
{'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob', 'person.name.civility': 'Dr'}

利用递归,保持简单和人类可读:

def flatten_dict(dictionary, accumulator=None, parent_key=None, separator="."):
if accumulator is None:
accumulator = {}


for k, v in dictionary.items():
k = f"{parent_key}{separator}{k}" if parent_key else k
if isinstance(v, dict):
flatten_dict(dictionary=v, accumulator=accumulator, parent_key=k)
continue


accumulator[k] = v


return accumulator

调用很简单:

new_dict = flatten_dict(dictionary)

new_dict = flatten_dict(dictionary, separator="_")

如果我们想改变默认分隔符。

稍微分解一下:

当函数第一次被调用时,它只被调用传递我们想要扁平化的dictionaryaccumulator参数在这里支持递归,稍后我们将看到。因此,我们将accumulator实例化到一个空字典中,我们将在其中放入来自原始dictionary的所有嵌套值。

if accumulator is None:
accumulator = {}

当我们遍历字典的值时,我们为每个值构造一个键。对于第一次调用,parent_key参数将是None,而对于每个嵌套字典,它将包含指向它的键,因此我们将该键放在前面。

k = f"{parent_key}{separator}{k}" if parent_key else k

如果键k指向的值v是一个字典,函数调用自身,传递嵌套字典、accumulator(通过引用传递,因此对它的所有更改都是在同一个实例上完成的)和键k,以便我们可以构造连接键。注意continue语句。我们希望跳过if块之外的下一行,这样嵌套的字典就不会在k键下的accumulator中结束。

if isinstance(v, dict):
flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
continue

那么,如果值v不是字典,我们该怎么办呢?只需要将它放在accumulator中。

accumulator[k] = v

一旦完成,我们只返回accumulator,原始的dictionary参数保持不变。

请注意

这只适用于有字符串作为键的字典。它将与实现__repr__方法的哈希对象一起工作,但将产生不想要的结果。

如果你不介意递归函数,这里有一个解决方案。我还冒昧地包含了一个排除-parameter,以防你想要维护一个或多个值。

代码:

def flatten_dict(dictionary, exclude = [], delimiter ='_'):
flat_dict = dict()
for key, value in dictionary.items():
if isinstance(value, dict) and key not in exclude:
flatten_value_dict = flatten_dict(value, exclude, delimiter)
for k, v in flatten_value_dict.items():
flat_dict[f"{key}{delimiter}{k}"] = v
else:
flat_dict[key] = value
return flat_dict

用法:

d = {'a':1, 'b':[1, 2], 'c':3, 'd':{'a':4, 'b':{'a':7, 'b':8}, 'c':6}, 'e':{'a':1,'b':2}}
flat_d = flatten_dict(dictionary=d, exclude=['e'], delimiter='.')
print(flat_d)

输出:

{'a': 1, 'b': [1, 2], 'c': 3, 'd.a': 4, 'd.b.a': 7, 'd.b.b': 8, 'd.c': 6, 'e': {'a': 1, 'b': 2}}

我尝试了本页上的一些解决方案-虽然不是全部-但我尝试的那些都无法处理dict的嵌套列表。

考虑这样一个词典:

d = {
'owner': {
'name': {'first_name': 'Steven', 'last_name': 'Smith'},
'lottery_nums': [1, 2, 3, 'four', '11', None],
'address': {},
'tuple': (1, 2, 'three'),
'tuple_with_dict': (1, 2, 'three', {'is_valid': False}),
'set': {1, 2, 3, 4, 'five'},
'children': [
{'name': {'first_name': 'Jessica',
'last_name': 'Smith', },
'children': []
},
{'name': {'first_name': 'George',
'last_name': 'Smith'},
'children': []
}
]
}
}

以下是我的临时解决方案:

def flatten_dict(input_node: dict, key_: str = '', output_dict: dict = {}):
if isinstance(input_node, dict):
for key, val in input_node.items():
new_key = f"{key_}.{key}" if key_ else f"{key}"
flatten_dict(val, new_key, output_dict)
elif isinstance(input_node, list):
for idx, item in enumerate(input_node):
flatten_dict(item, f"{key_}.{idx}", output_dict)
else:
output_dict[key_] = input_node
return output_dict

生产:

{
owner.name.first_name: Steven,
owner.name.last_name: Smith,
owner.lottery_nums.0: 1,
owner.lottery_nums.1: 2,
owner.lottery_nums.2: 3,
owner.lottery_nums.3: four,
owner.lottery_nums.4: 11,
owner.lottery_nums.5: None,
owner.tuple: (1, 2, 'three'),
owner.tuple_with_dict: (1, 2, 'three', {'is_valid': False}),
owner.set: {1, 2, 3, 4, 'five'},
owner.children.0.name.first_name: Jessica,
owner.children.0.name.last_name: Smith,
owner.children.1.name.first_name: George,
owner.children.1.name.last_name: Smith,
}

一个临时的解决方案,它不是完美的 注意:< / p >

  • 它不会保留空字典,例如address: {} k/v对。

  • 它不会将嵌套元组中的字典平铺——尽管使用python元组类似于列表的事实很容易添加它。

这不完全是OP所要求的,但很多人都来这里寻找方法来平坦现实世界的嵌套JSON数据,这些数据可以有嵌套的键值JSON对象和数组,数组内的JSON对象等等。JSON不包括元组,所以我们不必担心这些。

我找到了列表包含评论来自@roneo来自@Imran的回答的实现:

https://github.com/ScriptSmith/socialreaper/blob/master/socialreaper/tools.py#L8

import collections
def flatten(dictionary, parent_key=False, separator='.'):
"""
Turn a nested dictionary into a flattened dictionary
:param dictionary: The dictionary to flatten
:param parent_key: The string to prepend to dictionary's keys
:param separator: The string used to separate flattened keys
:return: A flattened dictionary
"""


items = []
for key, value in dictionary.items():
new_key = str(parent_key) + separator + key if parent_key else key
if isinstance(value, collections.MutableMapping):
items.extend(flatten(value, new_key, separator).items())
elif isinstance(value, list):
for k, v in enumerate(value):
items.extend(flatten({str(k): v}, new_key).items())
else:
items.append((new_key, value))
return dict(items)

测试:

flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3] })


>> {'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd.0': 1, 'd.1': 2, 'd.2': 3}

这做的工作,我需要做:我扔任何复杂的json在这,它为我扁平化。

所有学分归https://github.com/ScriptSmith

这里有一个使用堆栈的解决方案。没有递归。

def flatten_nested_dict(nested):
stack = list(nested.items())
ans = {}
while stack:
key, val = stack.pop()
if isinstance(val, dict):
for sub_key, sub_val in val.items():
stack.append((f"{key}_{sub_key}", sub_val))
else:
ans[key] = val
return ans

你可以使用递归来平展你的字典。

import collections




def flatten(
nested_dict,
seperator='.',
name=None,
):
flatten_dict = {}


if not nested_dict:
return flatten_dict


if isinstance(
nested_dict,
collections.abc.MutableMapping,
):
for key, value in nested_dict.items():
if name is not None:
flatten_dict.update(
flatten(
nested_dict=value,
seperator=seperator,
name=f'{name}{seperator}{key}',
),
)
else:
flatten_dict.update(
flatten(
nested_dict=value,
seperator=seperator,
name=key,
),
)
else:
flatten_dict[name] = nested_dict


return flatten_dict




if __name__ == '__main__':
nested_dict = {
1: 'a',
2: {
3: 'c',
4: {
5: 'e',
},
6: [1, 2, 3, 4, 5, ],
},
}


print(
flatten(
nested_dict=nested_dict,
),
)

输出:

{
"1":"a",
"2.3":"c",
"2.4.5":"e",
"2.6":[1, 2, 3, 4, 5]
}

如果你是神谕的oneliners的粉丝:

my_dict={'a': 1,'c': {'a': 2,'b': {'x': 5,'y' : 10}},'d': [1, 2, 3]}


list(pd.json_normalize(my_dict).T.to_dict().values())[0]

返回:

{'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd': [1, 2, 3]}

如果你有一个字典列表,而不仅仅是一个字典,你可以从结尾保留[0]

使用flatdict库:

dic={'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}


import flatdict
f =  flatdict.FlatDict(dic,delimiter='_')
print(f)
#output
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}
def flatten(dictionary, prefix = '', separator = '_'):
out_dict = {}
if type(dictionary) != dict:
out_dict[prefix] = dictionary
return out_dict
elif dictionary is None:
return None
for k in dictionary.keys():
if prefix:
prefix_n = prefix + f'{separator}{k}'
else:
prefix_n = k
out_dict.update(flatten_new(dictionary[k], prefix_n))
return out_dict

输出:

{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}