Giả sử bạn có một từ điển như:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Làm thế nào bạn sẽ đi về làm phẳng nó thành một cái gì đó như:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Giả sử bạn có một từ điển như:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Làm thế nào bạn sẽ đi về làm phẳng nó thành một cái gì đó như:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Câu trả lời:
Về cơ bản giống như cách bạn làm phẳng một danh sách lồng nhau, bạn chỉ cần thực hiện thêm công việc để lặp lại chính tả bằng khóa / giá trị, tạo khóa mới cho từ điển mới của bạn và tạo từ điển ở bước cuối cùng.
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}
isinstance
bằng một try..except
khối, điều này sẽ hoạt động cho bất kỳ ánh xạ nào, ngay cả khi nó không xuất phát từ dict
.
collections.MutableMapping
để làm cho nó chung chung hơn. Nhưng đối với Python <2.6, try..except
có lẽ là lựa chọn tốt nhất.
if isinstance(v, collections.MutableMapping):
thànhif v and isinstance(v, collections.MutableMapping):
new_key = parent_key + sep + k if parent_key else k
giả định rằng các khóa luôn là chuỗi, nếu không nó sẽ tăng TypeError: cannot concatenate 'str' and [other] objects
. Tuy nhiên, bạn có thể khắc phục điều đó bằng cách đơn giản là ép buộc k
chuỗi ( str(k)
) hoặc nối các khóa thành một tuple thay vì một chuỗi (tuples cũng có thể là các phím chính tả).
Có hai cân nhắc lớn mà người đăng ban đầu cần xem xét:
{'a_b':{'c':1}, 'a':{'b_c':2}}
sẽ dẫn đến {'a_b_c':???}
. Giải pháp dưới đây tránh được vấn đề bằng cách trả về một cặp lặp.joinedKey = '_'.join(*keys)
, điều đó sẽ khiến bạn mất O (N ^ 2) thời gian chạy. Tuy nhiên, nếu bạn sẵn sàng nói nextKey = previousKey+'_'+thisKey
, điều đó giúp bạn có thời gian O (N). Giải pháp dưới đây cho phép bạn thực hiện cả hai (vì bạn chỉ có thể ghép tất cả các khóa, sau đó xử lý hậu kỳ chúng).(Hiệu suất không phải là vấn đề, nhưng tôi sẽ giải thích về điểm thứ hai trong trường hợp bất kỳ ai khác quan tâm: Khi thực hiện điều này, có rất nhiều lựa chọn nguy hiểm. Nếu bạn thực hiện điều này một cách đệ quy và mang lại và tái sản xuất, hoặc bất cứ điều gì tương đương nút nhiều hơn một lần (đó là khá dễ dàng để vô tình làm), bạn đang làm có khả năng (^ 2 N) công việc chứ không phải là O O (N). Đây là vì có thể bạn đang tính toán một chìa khóa a
rồi a_1
sau đó a_1_i
..., và sau đó tính toán a
sau a_1
đó a_1_ii
..., nhưng thực sự bạn không cần phải tính toán a_1
lại. Ngay cả khi bạn không tính toán lại, thì việc đánh giá lại nó (một cách tiếp cận 'theo cấp độ') cũng tệ như vậy. Một ví dụ điển hình là suy nghĩ về hiệu suất trên {1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}}
)
Dưới đây là một chức năng tôi đã viết flattenDict(d, join=..., lift=...)
có thể được điều chỉnh cho nhiều mục đích và có thể làm những gì bạn muốn. Đáng buồn là khá khó để tạo ra một phiên bản lười biếng của chức năng này mà không phải chịu các hình phạt về hiệu năng ở trên (nhiều nội dung python như chain.from_iterable không thực sự hiệu quả, mà tôi chỉ nhận ra sau khi thử nghiệm rộng rãi ba phiên bản khác nhau của mã này trước khi xử lý cái này).
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
Để hiểu rõ hơn những gì đang diễn ra, bên dưới là sơ đồ cho những người không quen thuộc reduce
(bên trái), còn được gọi là "gấp bên trái". Đôi khi nó được vẽ với giá trị ban đầu thay cho k0 (không phải là một phần của danh sách, được truyền vào hàm). Đây J
là join
chức năng của chúng tôi . Chúng tôi tiền xử lý mỗi k n với lift(k)
.
[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
Thực tế điều này giống như functools.reduce
, nhưng chức năng của chúng ta thực hiện điều này với tất cả các đường dẫn chính của cây.
>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)
Trình diễn (mà nếu không tôi sẽ đặt trong chuỗi):
>>> testData = {
'a':1,
'b':2,
'c':{
'aa':11,
'bb':22,
'cc':{
'aaa':111
}
}
}
from pprint import pprint as pp
>>> pp(dict( flattenDict(testData, lift=lambda x:(x,)) ))
{('a',): 1,
('b',): 2,
('c', 'aa'): 11,
('c', 'bb'): 22,
('c', 'cc', 'aaa'): 111}
>>> pp(dict( flattenDict(testData, join=lambda a,b:a+'_'+b) ))
{'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}
Hiệu suất:
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
... thở dài, đừng nghĩ rằng đó là lỗi của tôi ...
[ghi chú lịch sử không quan trọng do vấn đề kiểm duyệt]
Liên quan đến bản sao được cho là của Flatten một từ điển từ điển (sâu 2 cấp) của danh sách trong Python :
Giải pháp cho câu hỏi đó có thể được thực hiện theo cách này sorted( sum(flatten(...),[]) )
. Điều ngược lại là không thể: mặc dù đúng là các giá trị của flatten(...)
có thể được phục hồi từ bản sao bị cáo buộc bằng cách ánh xạ bộ tích lũy bậc cao hơn, người ta không thể khôi phục các khóa. (chỉnh sửa: Ngoài ra, câu hỏi của chủ sở hữu trùng lặp bị cáo buộc là hoàn toàn khác nhau, ở chỗ nó chỉ liên quan đến từ điển chính xác sâu 2 cấp, mặc dù một trong những câu trả lời trên trang đó đưa ra giải pháp chung.)
Hoặc nếu bạn đã sử dụng gấu trúc, bạn có thể làm json_normalize()
như vậy:
import pandas as pd
d = {'a': 1,
'c': {'a': 2, 'b': {'x': 5, 'y' : 10}},
'd': [1, 2, 3]}
df = pd.io.json.json_normalize(d, sep='_')
print(df.to_dict(orient='records')[0])
Đầu ra:
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}
Nếu bạn đang sử dụng, pandas
có một chức năng ẩn trong pandas.io.json._normalize
1 được gọi là nested_to_record
chính xác.
from pandas.io.json._normalize import nested_to_record
flat = nested_to_record(my_dict, sep='_')
1 Trong các phiên bản gấu trúc 0.24.x
và sử dụng cũ hơn pandas.io.json.normalize
(không có _
)
from pandas.io.json._normalize import nested_to_record
. Lưu ý dấu gạch dưới ( _
) trước normalize
.
0.25.x
, tôi đã cập nhật câu trả lời. :)
Đây là một loại thực hiện "chức năng", "một lớp". Nó là đệ quy, và dựa trên một biểu thức có điều kiện và một sự hiểu biết chính tả.
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 }
Kiểm tra:
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}
('hgf',2)
cho khóa thứ 2 trong các lần ném thử nghiệm của bạnTypeError
+
toán tử. Đối với bất cứ điều gì khác, bạn sẽ cần phải thích ứng prefix + separator + k
với lời gọi hàm thích hợp để soạn các đối tượng.
{'a_b':{'c':1}, 'a':{'b_c':2}}
{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
Mã số:
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,''))
Các kết quả:
$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
Tôi đang sử dụng python3.2, cập nhật cho phiên bản python của bạn.
lkey=''
trong định nghĩa hàm của bạn thay vì khi gọi hàm. Xem câu trả lời khác về vấn đề này.
Làm thế nào về một giải pháp chức năng và hiệu suất trong 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(),
{}
))
Điều này thậm chí còn hiệu quả hơn:
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(),
{}
))
Đang sử dụng:
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}
reduce
là tuyệt vời trong trường hợp bạn cần giảm từ điển. Tôi cập nhật câu trả lời. Nên nhìn thêm một chút pythonic bây giờ.
Điều này không bị giới hạn trong từ điển, nhưng mọi loại ánh xạ thực hiện .items (). Hơn nữa ist nhanh hơn vì nó tránh một điều kiện if. Tuy nhiên, các khoản tín dụng đến Imran:
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)
d
không phải là dict
một loại ánh xạ tùy chỉnh không thực hiện items
, chức năng của bạn sẽ thất bại ngay lúc đó và ở đó. Vì vậy, nó không hoạt động cho mọi loại ánh xạ mà chỉ những loại thực hiện items()
.
items
chưa? Tôi tò mò muốn xem một cái.
Giải pháp Python 3.3 của tôi sử dụng máy phát điện:
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}
Chức năng đơn giản để làm phẳng từ điển lồng nhau. Đối với Python 3, thay thế .iteritems()
bằng.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
Ý tưởng / yêu cầu là: Nhận từ điển phẳng mà không giữ khóa cha.
Ví dụ về cách sử dụng:
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}
Giữ chìa khóa cha mẹ cũng đơn giản.
Điều này tương tự với câu trả lời của cả imran và ralu. Nó không sử dụng một trình tạo, mà thay vào đó sử dụng đệ quy với một bao đóng:
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}
Giải pháp của Davoud rất hay nhưng không cho kết quả khả quan khi chính tả lồng nhau cũng chứa danh sách các ký tự, nhưng mã của anh ta được điều chỉnh cho trường hợp đó:
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)
type([])
để tránh lệnh gọi hàm cho mọi mục của dict
.
isinstance(v, list)
thay thế
Sử dụng đệ quy, giữ cho nó đơn giản và dễ đọc với con người:
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
Gọi rất đơn giản:
new_dict = flatten_dict(dictionary)
hoặc là
new_dict = flatten_dict(dictionary, separator="_")
nếu chúng ta muốn thay đổi dấu phân cách mặc định.
Một chút đổ vỡ:
Khi hàm được gọi lần đầu tiên, nó được gọi chỉ truyền qua dictionary
chúng ta muốn làm phẳng. Các accumulator
tham số là ở đây để hỗ trợ đệ quy, mà chúng ta thấy sau đó. Vì vậy, chúng tôi khởi tạo accumulator
một từ điển trống, nơi chúng tôi sẽ đặt tất cả các giá trị lồng nhau từ bản gốc dictionary
.
if accumulator is None:
accumulator = {}
Khi chúng tôi lặp lại các giá trị của từ điển, chúng tôi xây dựng một khóa cho mọi giá trị. Đối parent_key
số sẽ None
dành cho cuộc gọi đầu tiên, trong khi đối với mọi từ điển lồng nhau, nó sẽ chứa khóa trỏ đến nó, vì vậy chúng tôi bổ sung khóa đó.
k = f"{parent_key}{separator}{k}" if parent_key else k
Trong trường hợp giá trị v
mà khóa k
đang trỏ đến là một từ điển, hàm sẽ tự gọi nó, chuyển từ điển lồng nhau, accumulator
(được truyền bằng tham chiếu, do đó, tất cả các thay đổi được thực hiện trên cùng một ví dụ) và khóa k
để chúng ta có thể xây dựng khóa ghép. Chú ý continue
tuyên bố. Chúng tôi muốn bỏ qua dòng tiếp theo, bên ngoài if
khối, để từ điển lồng nhau không kết thúc trong accumulator
khóa dưới k
.
if isinstance(v, dict):
flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
continue
Vậy, chúng ta phải làm gì trong trường hợp giá trị v
không phải là từ điển? Chỉ cần đặt nó không thay đổi bên trong accumulator
.
accumulator[k] = v
Khi chúng ta đã hoàn thành, chúng ta chỉ cần trả lại accumulator
, không để lại dictionary
đối số ban đầu .
GHI CHÚ
Điều này sẽ chỉ hoạt động với các từ điển có chuỗi là khóa. Nó sẽ làm việc với các đối tượng có thể băm thực hiện __repr__
phương thức, nhưng sẽ mang lại kết quả không mong muốn.
Các câu trả lời trên hoạt động thực sự tốt. Chỉ cần nghĩ rằng tôi đã thêm chức năng unflatten mà tôi đã viết:
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
Lưu ý: Điều này không giải thích cho '_' đã có trong các khóa, giống như các đối tác làm phẳng.
Đây là một thuật toán để thay thế thanh lịch, tại chỗ. Đã thử nghiệm với Python 2.7 và Python 3.5. Sử dụng ký tự dấu chấm làm dấu phân cách.
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
Thí dụ:
d = {'a': {'b': 'c'}}
flatten_json(d)
print(d)
unflatten_json(d)
print(d)
Đầu ra:
{'a.b': 'c'}
{'a': {'b': 'c'}}
Tôi đã xuất bản mã này ở đây cùng với unflatten_json
chức năng phù hợp .
Nếu bạn muốn từ điển lồng nhau và muốn tất cả danh sách các khóa duy nhất thì đây là giải pháp:
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
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
Tôi đã nghĩ đến một lớp con của UserDict để tự động làm phẳng các phím.
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)
Những lợi thế mà các phím có thể được thêm vào một cách nhanh chóng, hoặc sử dụng chính tả chính tả, mà không có gì bất ngờ:
>>> 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'}
Sử dụng máy phát điện:
def flat_dic_helper(prepand,d):
if len(prepand) > 0:
prepand = prepand + "_"
for k in d:
i=d[k]
if type(i).__name__=='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}
type(i).__name__=='dict'
có thể được thay thế bằng type(i) is dict
hoặc thậm chí tốt hơn isinstance(d, dict)
(hoặc Mapping
/ MutableMapping
).
Sử dụng dict.popitem () trong đệ quy đơn giản giống như danh sách lồng nhau:
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)}
Không chính xác những gì OP yêu cầu, nhưng rất nhiều người đang đến đây để tìm cách làm phẳng dữ liệu JSON lồng nhau trong thế giới thực, có thể lồng các đối tượng json giá trị khóa và các mảng và đối tượng json bên trong mảng, v.v. JSON không bao gồm các bộ dữ liệu, vì vậy chúng tôi không phải lo lắng về những thứ đó.
Tôi tìm thấy cách triển khai bình luận bao gồm danh sách của @roneo cho câu trả lời được đăng bởi @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)
Kiểm tra nó:
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}
Và đó là công việc tôi cần thực hiện: Tôi ném bất kỳ json phức tạp nào vào đây và nó làm phẳng nó ra cho tôi.
Tất cả các khoản tín dụng cho https://github.com/ScriptSmith .
Tôi thực sự đã viết một gói được gọi là cherrypicker gần đây để đối phó với loại điều chính xác này vì tôi phải làm điều đó thường xuyên!
Tôi nghĩ đoạn mã sau sẽ cung cấp cho bạn chính xác những gì bạn đang theo đuổi:
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()
Bạn có thể cài đặt gói với:
pip install cherrypicker
... và có thêm tài liệu và hướng dẫn tại https://cherrypicker.readthedocs.io .
Các phương pháp khác có thể nhanh hơn, nhưng ưu tiên của gói này là làm cho các tác vụ đó trở nên dễ dàng . Nếu bạn có một danh sách lớn các đối tượng để làm phẳng, bạn cũng có thể yêu cầu CherryPicker sử dụng xử lý song song để tăng tốc mọi thứ.
Tôi luôn thích truy cập dict
các đối tượng thông qua .items()
, vì vậy để làm phẳng các dicts tôi sử dụng trình tạo đệ quy sau flat_items(d)
. Nếu bạn muốn có dict
một lần nữa, chỉ cần bọc nó như thế này:flat = dict(flat_items(d))
def flat_items(d, key_separator='.'):
"""
Flattens the dictionary containing other dictionaries like here: /programming/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
Biến thể của từ điển lồng nhau Flatten này , nén các phím với max_level và bộ giảm tốc tùy chỉnh.
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)
Nếu bạn không nhớ các hàm đệ quy, đây là một giải pháp. Tôi cũng đã lấy tự do để bao gồm một loại trừ trong trường hợp có một hoặc nhiều giá trị bạn muốn duy trì.
Mã số:
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
Sử dụng:
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)
Đầu ra:
{'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}}
Tôi đã thử một số giải pháp trên trang này - mặc dù không phải tất cả - nhưng những giải pháp tôi đã thử không thể xử lý danh sách dict lồng nhau.
Hãy xem xét một dict như thế này:
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': []
}
]
}
}
Đây là giải pháp tạm thời của tôi:
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
sản xuất:
{
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,
}
Một giải pháp tạm thời và nó không hoàn hảo.
GHI CHÚ:
nó không giữ các ký hiệu trống như address: {}
cặp k / v.
nó sẽ không làm phẳng các ký tự trong các bộ dữ liệu lồng nhau - mặc dù có thể dễ dàng thêm bằng cách sử dụng bộ dữ liệu trăn hoạt động tương tự như danh sách.
Chỉ cần sử dụng python-benedict
, nó là một lớp con dict cung cấp nhiều tính năng, bao gồm một flatten
phương thức. Có thể cài đặt nó bằng pip:pip install python-benedict
https://github.com/fabiocaccamo/python-benedict#flatten
from benedict import benedict
d = benedict(data)
f = d.flatten(separator='_')