Bir sözlüğünüz olduğunu varsayalım:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Bunu şöyle bir şeye düzleştirmeye nasıl devam edersiniz:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Bir sözlüğünüz olduğunu varsayalım:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Bunu şöyle bir şeye düzleştirmeye nasıl devam edersiniz:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
Yanıtlar:
Temel olarak, iç içe bir listeyi düzleştirdiğiniz gibi, dikteyi anahtar / değer ile yinelemek, yeni sözlüğünüz için yeni anahtarlar oluşturmak ve son adımda sözlük oluşturmak için ekstra iş yapmanız yeterlidir.
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
bir try..except
blok ile değiştirirseniz, türetilmemiş olsa bile herhangi bir eşleme için çalışır dict
.
collections.MutableMapping
Daha genel hale getirmek için test edilecek şekilde değiştirildi . Ancak Python <2.6 için, try..except
muhtemelen en iyi seçenektir.
if isinstance(v, collections.MutableMapping):
if v and isinstance(v, collections.MutableMapping):
new_key = parent_key + sep + k if parent_key else k
Anahtarların her zaman dize olduğunu varsaydığına dikkat edin, aksi takdirde yükselecektir TypeError: cannot concatenate 'str' and [other] objects
. Bununla birlikte, sadece k
string ( str(k)
) öğesine zorlayarak veya anahtarları bir dize yerine bir tuple içine birleştirerek düzeltebilirsiniz (tuples da dict tuşları olabilir).
Orijinal posterin dikkate alması gereken iki büyük husus vardır:
{'a_b':{'c':1}, 'a':{'b_c':2}}
sonuçlanır {'a_b_c':???}
. Aşağıdaki çözüm, yinelenebilir bir çift döndürerek sorunu ortadan kaldırır.joinedKey = '_'.join(*keys)
, bu size O (N ^ 2) çalışma süresine mal olacaktır. Ancak söylemeye istekli iseniz nextKey = previousKey+'_'+thisKey
, bu size O (N) zamanı kazandırır. Aşağıdaki çözüm her ikisini de yapmanıza izin verir (çünkü sadece tüm anahtarları birleştirebilir ve sonra bunları işleyebilirsiniz).(Performans muhtemelen bir sorun değildir, ancak başka birinin umurunda olması durumunda ikinci noktayı ele alacağım: Bunu uygularken, çok sayıda tehlikeli seçenek var. Bunu tekrar tekrar yaparsanız, verim ve yeniden verim veya dokunan eşdeğeri bir şey varsa Birden fazla düğüm (yanlışlıkla yapılması oldukça kolaydır), O (N) yerine potansiyel olarak O (N ^ 2) çalışması yapıyorsunuz, bunun nedeni belki de a
o a_1
zaman bir anahtar hesaplamanızdır a_1_i
... a
o a_1
zaman a_1_ii
..., ama gerçekten a_1
tekrar hesaplamak zorunda kalmamalısınız .. Yeniden hesaplamasanız bile, yeniden elde etmek ('seviye-seviye' yaklaşımı) kadar kötüdür. performansı hakkında düşünmek {1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}}
)
Aşağıda yazdığım flattenDict(d, join=..., lift=...)
, birçok amaca uyarlanabilen ve istediğinizi yapabilen bir işlev var . Ne yazık ki yukarıdaki performans cezalarına maruz kalmadan bu fonksiyonun tembel bir versiyonunu yapmak oldukça zordur (chain.from_iterable gibi birçok python yerleşimi aslında verimli değildir, ki bu kodun üç farklı versiyonunun kapsamlı testlerinden sonra Bu).
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
Neler olup bittiğini daha iyi anlamak için, aşağıda reduce
(solda) bilmeyenler için (aksi halde "sol katlama" olarak bilinen) bir diyagram bulunmaktadır . Bazen k0 yerine bir başlangıç değeri ile çizilir (listenin bir parçası değil, işleve geçirilir). İşte J
bizim join
fonksiyonumuz. Her k için ön işlemeden n ile 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
Bu aslında ile aynıdır functools.reduce
, ancak fonksiyonumuz bunu ağacın tüm anahtar yollarına yapar.
>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)
Gösteri (aksi takdirde doktora yaptığım):
>>> 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}
Verim:
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
... iç çek, birinin benim hatam olduğunu düşünme ...
[denetleme sorunları nedeniyle önemsiz tarihsel not]
Flatten'in iddia edilen kopyasıyla ilgili olarak, Python'daki listelerin sözlükleri (2 seviye derinlik) sözlüğü :
Bu sorunun çözümü bu açıdan uygulanabilir sorted( sum(flatten(...),[]) )
. Ters mümkün değildir: o doğrudur iken değerleri arasında flatten(...)
yüksek bir dereceden akümülatörü eşleyerek iddia edilen iki kopya kurtarıldı edilebilir bir anahtarlarını kurtaramazsınız. (değiştir: Ayrıca iddia edilen kopya sahibinin sorusunun tamamen farklı olduğu ortaya çıkıyor, çünkü bu sayfadaki cevaplardan biri genel bir çözüm sunuyor olsa da, sadece 2 seviyeli derinlikteki sözlüklerle ilgileniyor.)
Veya zaten pandalar kullanıyorsanız, bunu şu şekilde yapabilirsiniz json_normalize()
:
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])
Çıktı:
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}
Eğer kullanıyorsanız pandas
gizli bir fonksiyon vardır pandas.io.json._normalize
1 denilen nested_to_record
tam olarak bunu yapar.
from pandas.io.json._normalize import nested_to_record
flat = nested_to_record(my_dict, sep='_')
1 Panda versiyonlarında 0.24.x
ve eski kullanımda pandas.io.json.normalize
(olmadan _
)
from pandas.io.json._normalize import nested_to_record
. Daha _
önce alt çizgiye ( ) dikkat edin normalize
.
0.25.x
, cevabı güncelledim. :)
İşte bir tür "işlevsel", "tek katlı" uygulama. Özyinelemeli ve koşullu bir ifadeye ve dik bir kavrayışa dayanır.
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 }
Ölçek:
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)
, test atışlarınızda 2. anahtarın yerineTypeError
+
işleci destekleyen başka bir şey olduğunu varsayar . Başka bir şey prefix + separator + k
için nesneleri oluşturmak için uygun işlev çağrısına uyum sağlamanız gerekir .
{'a_b':{'c':1}, 'a':{'b_c':2}}
{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
Kod:
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,''))
Sonuçlar:
$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
Python sürümünüz için güncelleme python3.2 kullanıyorum.
lkey=''
, işlevi çağırmak yerine işlev tanımınızda varsayılan değerini belirtmek istersiniz . Bu konudaki diğer cevaplara bakınız.
Nasıl hakkında işlevsel Python3.5 ve ölçülebilir çözümü?
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(),
{}
))
Bu daha da performanslıdır:
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(),
{}
))
Kullanımda:
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
Sözlükleri azaltmanız gerektiğinde hala harika olduğunu düşünüyorum . Cevabı güncelledim. Şimdi biraz daha pitonik görünmeli.
Bu yalnızca sözlüklerle sınırlı değildir, .items () yöntemini uygulayan her eşleme türüyle sınırlıdır. Bir if koşulundan kaçındığı için daha hızlı istersiniz. Yine de krediler İmran'a gider:
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
olmayan bir dict
ama uygulamıyor özel bir haritalama tipi items
, sizin fonksiyon sonra ve orada başarısız olur. Bu nedenle, her haritalama türü için değil, yalnızca uygulayanlar için çalışır items()
.
items
mı? Birini görmek isterdim.
Jeneratörler kullanarak My Python 3.3 Çözümü:
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}
Basit ve insan tarafından okunabilir halde tutarak özyineleme kullanmak:
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
Çağrı basit:
new_dict = flatten_dict(dictionary)
veya
new_dict = flatten_dict(dictionary, separator="_")
varsayılan ayırıcıyı değiştirmek istiyorsak.
Biraz arıza:
Fonksiyon ilk olarak çağrıldığında, sadece dictionary
düzleştirmek istediğimizden geçmek denir . accumulator
Parametre Daha sonra göreceğimiz destek özyineleme, için burada. Bu nedenle, accumulator
iç içe geçmiş tüm değerleri orijinalden koyacağımız boş bir sözlüğe başlarız dictionary
.
if accumulator is None:
accumulator = {}
Sözlüğün değerlerini yineledikçe, her değer için bir anahtar oluştururuz. parent_key
Argüman olacak None
o tuşa prepend yani her iç içe sözlük için, bu o işaret anahtarı içermesi olurken, ilk çağrı için.
k = f"{parent_key}{separator}{k}" if parent_key else k
Durumda değer v
anahtarı k
işaret ettiği bir sözlük olduğu, fonksiyon, iç içe sözlüğü geçen kendisini çağıran accumulator
(kendisine yapılan tüm değişiklikler aynı örneğinde yapılır, böylece referans olarak geçirilir olan) ve anahtar k
böylece bizim birlikte birleştirilmiş anahtarı oluşturabilir. İfadeye dikkat edin continue
. Bir sonraki satırı, if
bloğun dışında atlamak istiyoruz , böylece içiçe sözlük accumulator
alt anahtarda kalmayacakk
.
if isinstance(v, dict):
flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
continue
Öyleyse, değerin v
sözlük olmaması durumunda ne yapacağız ? Sadece değişmeden içine koy accumulator
.
accumulator[k] = v
İşimiz bittikten accumulator
sonra, orijinal dictionary
argümana dokunulmadan geri dönüyoruz.
NOT
Bu, yalnızca anahtar dizesi olan sözlüklerde işe yarar. __repr__
Yöntemi uygulayan yıkanabilir nesnelerle çalışır , ancak istenmeyen sonuçlar verir.
İç içe sözlükleri düzleştirmek için basit bir işlev. Python 3 için, yerini .iteritems()
ile.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
Fikir / gereksinim şöyleydi: Üst anahtar bulundurmadan düz sözlükler alın.
Kullanım örneği:
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}
Üst anahtarları tutmak da basittir.
Bu hem imran hem de ralu'nun cevabına benzer. Bir jeneratör kullanmaz, bunun yerine bir kapakla özyineleme kullanır:
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}
Davoud'un çözümü çok güzel ama iç içe dikte aynı zamanda dikte listeleri içerdiğinde tatmin edici sonuçlar vermiyor, ancak kodu bu durum için uyarlandı:
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([])
her öğesi için bir işlev çağrısından kaçınmanın sonucunu önbelleğe alabilirsiniz dict
.
isinstance(v, list)
bunun yerine kullanın
Yukarıdaki cevaplar gerçekten işe yarıyor. Yazdığım düz olmayan fonksiyonu ekleyeceğimi düşündüm:
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
Not: Bu, düzleştirilmiş meslektaşları gibi tuşlarda zaten bulunan '_' ifadesini içermez.
İşte zarif, yerinde değiştirme için bir algoritma. Python 2.7 ve Python 3.5 ile test edilmiştir. Nokta karakterini ayırıcı olarak kullanma.
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
Misal:
d = {'a': {'b': 'c'}}
flatten_json(d)
print(d)
unflatten_json(d)
print(d)
Çıktı:
{'a.b': 'c'}
{'a': {'b': 'c'}}
Eşleme fonksiyonu ile birlikte bu kodu burada yayınladım unflatten_json
.
Eğer iç içe sözlük düz ve tüm benzersiz anahtarlar listesi istiyorsanız o zaman çözüm İşte:
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
Anahtarları otomatik olarak düzleştirmek için UserDict bir alt sınıf düşünüyordum.
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)
Keys Anahtarların anında eklenebilmesinin veya standart diksiyon örneğinin kullanılmasıyla sürpriz yapılmasının avantajları:
>>> 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'}
Jeneratörleri kullanma:
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'
ile değiştirilebilir type(i) is dict
veya belki de daha iyisi olabilir isinstance(d, dict)
(veya Mapping
/ MutableMapping
).
Basit iç içe geçmiş liste benzeri özyinelemede dict.popitem () kullanımı:
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)}
Tam olarak OP'nin sorduğu gibi değil, ancak birçok kişi buraya, anahtar değer json nesnelerini ve dizileri ve dizilerin içindeki json nesnelerini iç içe yerleştirmiş olabilecek gerçek dünya iç içe JSON verilerini düzleştirmenin yollarını arıyor. JSON tuples içermez, bu yüzden bunlardan korkmak zorunda değiliz.
Listenin-içerme bir uygulama buldum @roneo tarafından yorumun için @Imran tarafından gönderildi cevap :
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)
Dene:
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}
Yapmam gereken işi yapan ve bu konuda karmaşık bir json atıyorum ve bunu benim için düzleştiriyor.
Https://github.com/ScriptSmith'e yapılan tüm krediler .
Aslında son zamanlarda bu tür bir şeyle başa çıkmak için cherrypicker adlı bir paket yazdım çünkü sık sık yapmak zorunda kaldım!
Aşağıdaki kod size tam olarak ne peşinde olduğunu düşünüyorum:
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()
Paketi aşağıdakilerle kurabilirsiniz:
pip install cherrypicker
... ve https://cherrypicker.readthedocs.io adresinde daha fazla doküman ve rehberlik var .
Diğer yöntemler daha hızlı olabilir, ancak bu paketin önceliği bu tür görevleri kolaylaştırmaktır . Düzleştirmek için geniş bir nesne listeniz varsa, CherryPicker'a işleri hızlandırmak için paralel işleme kullanmasını da söyleyebilirsiniz.
Erişim dict
nesnelerini her zaman üzerinden tercih ederim .items()
, bu yüzden dikte etmek için aşağıdaki özyinelemeli jeneratörü kullanıyorum flat_items(d)
. dict
Tekrar sahip olmak istiyorsanız , sadece şu şekilde sarın: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
Bu düzleştirilmiş iç içe sözlüklerin varyasyonu, tuşları max_level ve özel redüktör ile sıkıştırma .
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)
Özyinelemeli işlevlere aldırmazsanız, işte bir çözüm. Ayrıca bir dışlama dahil etme özgürlüğünü de aldım , korumak istediğiniz bir veya daha fazla değer olması durumunda parametresi .
Kod:
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
Kullanımı:
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)
Çıktı:
{'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}}
Bu sayfadaki çözümlerin bazılarını denedim - hepsi olmasa da - ama denediklerim iç içe geçmiş diksiyon listesini ele alamadı.
Bunun gibi bir diksiyon düşünün:
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': []
}
]
}
}
İşte geçici çözümüm:
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
hangi üretir:
{
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,
}
Derme çatma bir çözüm ve mükemmel değil.
NOT:
address: {}
k / v çifti gibi boş dikte tutmaz .
iç içe tuples dikte düzleştirmek olmaz - python tuples listelerine benzer hareket gerçeğini kullanarak eklemek kolay olurdu.
Sadece kullanın python-benedict
, bir flatten
yöntem dahil olmak üzere birçok özellik sunan bir dikte alt sınıfıdır . Pip kullanarak kurmak mümkündür:pip install python-benedict
https://github.com/fabiocaccamo/python-benedict#flatten
from benedict import benedict
d = benedict(data)
f = d.flatten(separator='_')