İç içe sözlük öğelerine bir anahtar listesi üzerinden erişilsin mi?


143

Doğru öğeyi adreslemek için bir anahtar listesi üzerinden erişmek istiyorum karmaşık bir sözlük yapısı var.

dataDict = {
    "a":{
        "r": 1,
        "s": 2,
        "t": 3
        },
    "b":{
        "u": 1,
        "v": {
            "x": 1,
            "y": 2,
            "z": 3
        },
        "w": 3
        }
}    

maplist = ["a", "r"]

veya

maplist = ["b", "v", "y"]

Çalışan aşağıdaki kodu yaptık ama eminim kimse bir fikri varsa bunu yapmak için daha iyi ve daha etkili bir yol.

# Get a given data from a dictionary with position provided as a list
def getFromDict(dataDict, mapList):    
    for k in mapList: dataDict = dataDict[k]
    return dataDict

# Set a given data in a dictionary with position provided as a list
def setInDict(dataDict, mapList, value): 
    for k in mapList[:-1]: dataDict = dataDict[k]
    dataDict[mapList[-1]] = value

Yanıtlar:


230

reduce()Sözlüğü değiştirmek için kullanın :

from functools import reduce  # forward compatibility for Python 3
import operator

def getFromDict(dataDict, mapList):
    return reduce(operator.getitem, mapList, dataDict)

ve getFromDictdeğerinin saklanacağı yeri bulmak için yeniden kullanın setInDict():

def setInDict(dataDict, mapList, value):
    getFromDict(dataDict, mapList[:-1])[mapList[-1]] = value

mapListDeğeri eklemek üzere 'üst' sözlüğü bulmak için son öğe hariç tümü gereklidir, ardından değeri sağ tuşa ayarlamak için son öğeyi kullanın.

Demo:

>>> getFromDict(dataDict, ["a", "r"])
1
>>> getFromDict(dataDict, ["b", "v", "y"])
2
>>> setInDict(dataDict, ["b", "v", "w"], 4)
>>> import pprint
>>> pprint.pprint(dataDict)
{'a': {'r': 1, 's': 2, 't': 3},
 'b': {'u': 1, 'v': {'w': 4, 'x': 1, 'y': 2, 'z': 3}, 'w': 3}}

Python PEP8 stil kılavuzunun işlevler için yılan_kutu adları yazdığını unutmayın . Yukarıdakiler listeler veya sözlükler ve listelerin bir karışımı için eşit derecede iyi çalışır, bu nedenle isimler gerçekten olmalı get_by_path()ve set_by_path():

from functools import reduce  # forward compatibility for Python 3
import operator

def get_by_path(root, items):
    """Access a nested object in root by item sequence."""
    return reduce(operator.getitem, items, root)

def set_by_path(root, items, value):
    """Set a value in a nested object in root by item sequence."""
    get_by_path(root, items[:-1])[items[-1]] = value

1
Bu tür çapraz geçişler keyfi iç içe yapılar için güvenilirdir? İç içe listelerle karışık sözlükler için de geçerli olacak mı? GetFromDict () öğesini default_value sağlamak ve default_value değerini None olarak değiştirmek için nasıl değiştirebilirim? Ben uzun yıllar PHP geliştirme ve C geliştirme öncesi Python acemi.
Dmitriy Sintsov

2
Ayrıca iç içe eşlenen küme, var olmayan düğümler, tam sayı anahtarları için imo: listeler, dize anahtarları için sözlükler oluşturmalıdır.
Dmitriy Sintsov

1
@ user1353510: olduğu gibi, burada normal dizinleme sözdizimi kullanılır, bu nedenle sözlüklerdeki listeleri de destekler. Bunlar için tamsayı dizinleri girmeniz yeterlidir.
Martijn Pieters

1
@ user1353510: Bir varsayılan değer, kullanım için try:, except (KeyError, IndexError): return default_valueakım etrafında returnhattı.
Martijn Pieters

1
@Georgy: using dict.get(), eksik isimler için Noneyükseltmek yerine semantiği değiştirir KeyError. Sonraki isimler daha sonra bir AttributeError. operatorstandart bir kütüphanedir, burada kaçınmaya gerek yoktur.
Martijn Pieters

40
  1. Kabul edilen çözüm doğrudan python3 için çalışmaz - bir gerekir from functools import reduce.
  2. Ayrıca bir fordöngü kullanmak daha pitonik görünüyor . Python 3.0'daki Yenilikler'den alıntıya bakın .

    Kaldırıldı reduce(). functools.reduce()Gerçekten ihtiyacınız varsa kullanın ; ancak, zamanın yüzde 99'u açık bir fordöngü daha okunabilir.

  3. Sonra, kabul edilen çözüm mevcut olmayan iç içe anahtarları ayarlamaz (a döndürür KeyError) - bir çözüm için @ eafit'in cevabına bakın

Öyleyse neden bir değer elde etmek için kolergy'nin sorusundan önerilen yöntemi kullanmıyorsunuz:

def getFromDict(dataDict, mapList):    
    for k in mapList: dataDict = dataDict[k]
    return dataDict

Ve @ eafit'in bir değer belirleme cevabındaki kod:

def nested_set(dic, keys, value):
    for key in keys[:-1]:
        dic = dic.setdefault(key, {})
    dic[keys[-1]] = value

Her ikisi de python 2 ve 3'te düz çalışır


6
Bu çözümü tercih ediyorum - ama dikkatli ol. Yanılmıyorsam, Python sözlükleri değişmez getFromDictolduğundan arayanın yok olma potansiyeli vardır dataDict. copy.deepcopy(dataDict)Önce ben yaparım . Tabii ki, (yazılı olarak) ikinci fonksiyonda bu davranış istenir.
Dylan F

15

Reduce kullanımı akıllıdır, ancak üst anahtarlar iç içe sözlükte önceden mevcut değilse OP'nin ayar yönteminde sorunlar olabilir. Bu, google aramamda bu konu için gördüğüm ilk SO yayını olduğundan, biraz daha iyi yapmak istiyorum.

(Endeksler ve değerlerin bir listesi verildiğinde iç içe bir python sözlüğünde bir değer ayarlama) içindeki ayar yöntemi, eksik ebeveyn anahtarlarına karşı daha sağlam görünüyor. Üzerine kopyalamak için:

def nested_set(dic, keys, value):
    for key in keys[:-1]:
        dic = dic.setdefault(key, {})
    dic[keys[-1]] = value

Ayrıca, anahtar ağacını gezen ve oluşturduğum tüm mutlak anahtar yollarını elde eden bir yönteme sahip olmak uygun olabilir:

def keysInDict(dataDict, parent=[]):
    if not isinstance(dataDict, dict):
        return [tuple(parent)]
    else:
        return reduce(list.__add__, 
            [keysInDict(v,parent+[k]) for k,v in dataDict.items()], [])

Bunun bir kullanımı, aşağıdaki kodu kullanarak (iç içe sözlükteki tüm yaprakların aynı derinliğe sahip olduğu varsayılarak) iç içe ağacı panda DataFrame'e dönüştürmektir.

def dict_to_df(dataDict):
    ret = []
    for k in keysInDict(dataDict):
        v = np.array( getFromDict(dataDict, k), )
        v = pd.DataFrame(v)
        v.columns = pd.MultiIndex.from_product(list(k) + [v.columns])
        ret.append(v)
    return reduce(pd.DataFrame.join, ret)

'key' argüman uzunluğunu neden keyfi olarak 2 veya daha fazla ile sınırlandırmalıyım nested_set?
alancalvitti


3

Özyinelemeli işlevleri kullanmaya ne dersiniz?

Bir değer elde etmek için:

def getFromDict(dataDict, maplist):
    first, rest = maplist[0], maplist[1:]

    if rest: 
        # if `rest` is not empty, run the function recursively
        return getFromDict(dataDict[first], rest)
    else:
        return dataDict[first]

Ve bir değer ayarlamak için:

def setInDict(dataDict, maplist, value):
    first, rest = maplist[0], maplist[1:]

    if rest:
        try:
            if not isinstance(dataDict[first], dict):
                # if the key is not a dict, then make it a dict
                dataDict[first] = {}
        except KeyError:
            # if key doesn't exist, create one
            dataDict[first] = {}

        setInDict(dataDict[first], rest, value)
    else:
        dataDict[first] = value

2

Herhangi bir ithalat olmadan saf Python tarzı:

def nested_set(element, value, *keys):
    if type(element) is not dict:
        raise AttributeError('nested_set() expects dict as first argument.')
    if len(keys) < 2:
        raise AttributeError('nested_set() expects at least three arguments, not enough given.')

    _keys = keys[:-1]
    _element = element
    for key in _keys:
        _element = _element[key]
    _element[keys[-1]] = value

example = {"foo": { "bar": { "baz": "ok" } } }
keys = ['foo', 'bar']
nested_set(example, "yay", *keys)
print(example)

Çıktı

{'foo': {'bar': 'yay'}}

2

Anahtarlardan biri yoksa hataları yükseltmek istemiyorsanız alternatif bir yol (böylece ana kodunuz kesintisiz çalışabilir):

def get_value(self,your_dict,*keys):
    curr_dict_ = your_dict
    for k in keys:
        v = curr_dict.get(k,None)
        if v is None:
            break
        if isinstance(v,dict):
            curr_dict = v
    return v

Bu durumda, giriş tuşlarından herhangi biri yoksa, alternatif bir görevi gerçekleştirmek için ana kodunuzda bir kontrol olarak kullanılabilecek Hiçbiri döndürülmez.


1

Her değer aramak istediğinizde bir performans isabeti almak yerine, sözlüğü bir kez düzleştirmeye ve ardından şu tuşa bakmaya ne dersiniz? b:v:y

def flatten(mydict):
  new_dict = {}
  for key,value in mydict.items():
    if type(value) == dict:
      _dict = {':'.join([key, _key]):_value for _key, _value in flatten(value).items()}
      new_dict.update(_dict)
    else:
      new_dict[key]=value
  return new_dict

dataDict = {
"a":{
    "r": 1,
    "s": 2,
    "t": 3
    },
"b":{
    "u": 1,
    "v": {
        "x": 1,
        "y": 2,
        "z": 3
    },
    "w": 3
    }
}    

flat_dict = flatten(dataDict)
print flat_dict
{'b:w': 3, 'b:u': 1, 'b:v:y': 2, 'b:v:x': 1, 'b:v:z': 3, 'a:r': 1, 'a:s': 2, 'a:t': 3}

Bu şekilde flat_dict['b:v:y'], size1 .

Ve her bir aramada sözlüğü çevirmek yerine, sözlüğü düzleştirip çıktıyı kaydederek bunu hızlandırabilirsiniz, böylece soğuk başlangıçtan bir arama düzleştirilmiş sözlüğü yüklemek ve basitçe hayır ile bir anahtar / değer araması yapmak anlamına gelir geçişi.


1

Bunu özyineleme ile çözdü:

def get(d,l):
    if len(l)==1: return d[l[0]]
    return get(d[l[0]],l[1:])

Örneğinizi kullanma:

dataDict = {
    "a":{
        "r": 1,
        "s": 2,
        "t": 3
        },
    "b":{
        "u": 1,
        "v": {
            "x": 1,
            "y": 2,
            "z": 3
        },
        "w": 3
        }
}
maplist1 = ["a", "r"]
maplist2 = ["b", "v", "y"]
print(get(dataDict, maplist1)) # 1
print(get(dataDict, maplist2)) # 2

1

Tüm dizinleri iki kez işlemeden dikte öğesini kontrol edip ayarlamaya ne dersiniz?

Çözüm:

def nested_yield(nested, keys_list):
    """
    Get current nested data by send(None) method. Allows change it to Value by calling send(Value) next time
    :param nested: list or dict of lists or dicts
    :param keys_list: list of indexes/keys
    """
    if not len(keys_list):  # assign to 1st level list
        if isinstance(nested, list):
            while True:
                nested[:] = yield nested
        else:
            raise IndexError('Only lists can take element without key')


    last_key = keys_list.pop()
    for key in keys_list:
        nested = nested[key]

    while True:
        try:
            nested[last_key] = yield nested[last_key]
        except IndexError as e:
            print('no index {} in {}'.format(last_key, nested))
            yield None

Örnek iş akışı:

ny = nested_yield(nested_dict, nested_address)
data_element = ny.send(None)
if data_element:
    # process element
    ...
else:
    # extend/update nested data
    ny.send(new_data_element)
    ...
ny.close()

Ölçek

>>> cfg= {'Options': [[1,[0]],[2,[4,[8,16]]],[3,[9]]]}
    ny = nested_yield(cfg, ['Options',1,1,1])
    ny.send(None)
[8, 16]
>>> ny.send('Hello!')
'Hello!'
>>> cfg
{'Options': [[1, [0]], [2, [4, 'Hello!']], [3, [9]]]}
>>> ny.close()

1

Partiye çok geç, ancak gelecekte birisine yardımcı olabilir. Benim kullanım durumum için, aşağıdaki işlev en iyi çalıştı. Herhangi bir veri türünü sözlükten çıkarmak için çalışır

dict değerimizi içeren sözlüktür

liste , değerimize yönelik "adımların" bir listesidir

def getnestedvalue(dict, list):

    length = len(list)
    try:
        for depth, key in enumerate(list):
            if depth == length - 1:
                output = dict[key]
                return output
            dict = dict[key]
    except (KeyError, TypeError):
        return None

    return None

1

Yuvalanmış öznitelikleri ayarlamak ve almak için iki statik yönteme sahip olmak için bu yanıtları görmek tatmin edicidir. Bu çözümler yuvalanmış ağaçları kullanmaktan çok daha iyidir https://gist.github.com/hrldcpr/2012250

İşte benim uygulama.

Kullanımı :

İç içe nitelik çağrısı ayarlamak için sattr(my_dict, 1, 2, 3, 5) is equal to my_dict[1][2][3][4]=5

Yuvalanmış bir öznitelik çağrısı almak için gattr(my_dict, 1, 2)

def gattr(d, *attrs):
    """
    This method receives a dict and list of attributes to return the innermost value of the give dict       
    """
    try:
        for at in attrs:
            d = d[at]
        return d
    except(KeyError, TypeError):
        return None


def sattr(d, *attrs):
    """
    Adds "val" to dict in the hierarchy mentioned via *attrs
    For ex:
    sattr(animals, "cat", "leg","fingers", 4) is equivalent to animals["cat"]["leg"]["fingers"]=4
    This method creates necessary objects until it reaches the final depth
    This behaviour is also known as autovivification and plenty of implementation are around
    This implementation addresses the corner case of replacing existing primitives
    https://gist.github.com/hrldcpr/2012250#gistcomment-1779319
    """
    for attr in attrs[:-2]:
        if type(d.get(attr)) is not dict:
            d[attr] = {}
        d = d[attr]
    d[attrs[-2]] = attrs[-1]

1

python-benedictTuş yolunu kullanarak iç içe öğelere erişmek için kullanmanızı öneririm .

Kullanarak yükleyin pip:

pip install python-benedict

Sonra:

from benedict import benedict

dataDict = benedict({
    "a":{
        "r": 1,
        "s": 2,
        "t": 3,
    },
    "b":{
        "u": 1,
        "v": {
            "x": 1,
            "y": 2,
            "z": 3,
        },
        "w": 3,
    },
}) 

print(dataDict['a.r'])
# or
print(dataDict['a', 'r'])

İşte tüm belgeler: https://github.com/fabiocaccamo/python-benedict


0

Ayrıca iç içe listeler ve dikteler dahil keyfi json ile çalışma ve geçersiz arama yollarını güzel bir şekilde işlemek istiyorsanız, işte benim çözümüm:

from functools import reduce


def get_furthest(s, path):
    '''
    Gets the furthest value along a given key path in a subscriptable structure.

    subscriptable, list -> any
    :param s: the subscriptable structure to examine
    :param path: the lookup path to follow
    :return: a tuple of the value at the furthest valid key, and whether the full path is valid
    '''

    def step_key(acc, key):
        s = acc[0]
        if isinstance(s, str):
            return (s, False)
        try:
            return (s[key], acc[1])
        except LookupError:
            return (s, False)

    return reduce(step_key, path, (s, True))


def get_val(s, path):
    val, successful = get_furthest(s, path)
    if successful:
        return val
    else:
        raise LookupError('Invalid lookup path: {}'.format(path))


def set_val(s, path, value):
    get_val(s, path[:-1])[path[-1]] = value

0

dizeleri birleştirmek için bir yöntem:

def get_sub_object_from_path(dict_name, map_list):
    for i in map_list:
        _string = "['%s']" % i
        dict_name += _string
    value = eval(dict_name)
    return value
#Sample:
_dict = {'new': 'person', 'time': {'for': 'one'}}
map_list = ['time', 'for']
print get_sub_object_from_path("_dict",map_list)
#Output:
#one

0

@DomTomCat ve diğerlerinin yaklaşımını genişleten bu işlevler (yani, girdiyi etkilemeden değiştirilmiş verileri derin kopya ile döndürür) ayarlayıcı ve eşleştirici iç içe dictve için çalışır list.

setter:

def set_at_path(data0, keys, value):
    data = deepcopy(data0)
    if len(keys)>1:
        if isinstance(data,dict):
            return {k:(set_by_path(v,keys[1:],value) if k==keys[0] else v) for k,v in data.items()}
        if isinstance(data,list):
            return [set_by_path(x[1],keys[1:],value) if x[0]==keys[0] else x[1] for x in enumerate(data)]
    else:
        data[keys[-1]]=value
        return data

mapper:

def map_at_path(data0, keys, f):
    data = deepcopy(data0)
    if len(keys)>1:
        if isinstance(data,dict):
            return {k:(map_at_path(v,keys[1:],f) if k==keys[0] else v) for k,v in data.items()}
        if isinstance(data,list):
            return [map_at_path(x[1],keys[1:],f) if x[0]==keys[0] else x[1] for x in enumerate(data)]
    else:
        data[keys[-1]]=f(data[keys[-1]])
        return data

0

Bu evalişlevi python'da kullanabilirsiniz.

def nested_parse(nest, map_list):
    nestq = "nest['" + "']['".join(map_list) + "']"
    return eval(nestq, {'__builtins__':None}, {'nest':nest})

açıklama

Örnek sorgunuz için: maplist = ["b", "v", "y"]

nestqiç içe sözlük "nest['b']['v']['y']"nerede olacak nest.

evalYerleşiği fonksiyonu verilen dize yürütür. Bununla birlikte, evalişlev kullanımından kaynaklanan olası güvenlik açıklarına dikkat etmek önemlidir . Tartışma burada bulunabilir:

  1. https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
  2. https://www.journaldev.com/22504/python-eval-function

In nested_parse()fonksiyonu, hiçbir emin yapmış __builtins__globaller mevcut ve kullanılabilir olduğu yalnızca yerel değişkendir nestsözlük.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.