कुंजियों की सूची के माध्यम से नेस्टेड डिक्शनरी आइटम एक्सेस करें?


143

मेरे पास एक जटिल शब्दकोश संरचना है जिसे मैं सही आइटम को संबोधित करने के लिए कुंजियों की एक सूची के माध्यम से एक्सेस करना चाहूंगा।

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

maplist = ["a", "r"]

या

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

मैंने निम्नलिखित कोड बनाया है जो काम करता है, लेकिन मुझे यकीन है कि अगर किसी के पास एक विचार है तो यह करने के लिए एक बेहतर और अधिक कुशल तरीका है।

# 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

जवाबों:


230

reduce()शब्दकोश का प्रयोग करें :

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

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

और getFromDictमान को संग्रहीत करने के लिए स्थान खोजने के लिए पुन: उपयोग करें setInDict():

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

mapListमूल्य को जोड़ने के लिए 'मूल' शब्दकोष को खोजने के लिए अंतिम तत्व की आवश्यकता होती है, फिर अंतिम तत्व का उपयोग मूल्य को सही कुंजी पर सेट करने के लिए करते हैं।

डेमो:

>>> 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}}

ध्यान दें कि पायथन PEP8 शैली गाइड कार्यों के लिए साँप_केस नाम निर्धारित करता है । उपरोक्त सूचियों या शब्दकोशों और सूचियों के मिश्रण के लिए समान रूप से अच्छी तरह से काम करता है, इसलिए नाम वास्तव में होने चाहिए get_by_path()और 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
मनमाना नेस्टेड संरचनाओं के लिए इस तरह के ट्रैवर्सिंग कितना विश्वसनीय है? क्या यह नेस्टेड सूचियों के साथ मिश्रित शब्दकोशों के लिए भी काम करेगा? मैं DefaultFvalDict की आपूर्ति करने के लिए getFromDict () को कैसे संशोधित करूं और किसी के रूप में डिफ़ॉल्ट default_value हो? मैं कई वर्षों के पीएचपी विकास और सी विकास से पहले पायथन में नौसिखिया हूं।
दिमित्रि सिन्टसोव

2
नेस्टेड मैप्ड सेट को गैर-मौजूदा नोड्स बनाना चाहिए, imo: पूर्णांक कुंजियों के लिए सूचियाँ, स्ट्रिंग कुंजियों के लिए शब्दकोष।
दिमित्री सिन्तसोव

1
@ user1353510: जैसा कि होता है, नियमित अनुक्रमण सिंटैक्स का उपयोग यहां किया जाता है, इसलिए यह शब्दकोशों के अंदर भी सूचियों का समर्थन करेगा। बस उन लोगों के लिए पूर्णांक अनुक्रमित में पास करें।
मार्टिन पीटर्स

1
@ user1353510: एक डिफ़ॉल्ट मान के लिए try:, except (KeyError, IndexError): return default_valueवर्तमान returnलाइन के चारों ओर , उपयोग करें ।
मार्टिन पीटर्स

1
@Georgy: dict.get()सिमेंटिक्स में परिवर्तन का उपयोग करते हुए, जैसे कि लापता नामों के लिए Noneजुटाने के बजाय रिटर्न KeyError। बाद के किसी भी नाम को ट्रिगर किया जाता है AttributeErroroperatorएक मानक पुस्तकालय है, यहां इसे टालने की कोई आवश्यकता नहीं है।
मार्टिन पीटर्स

40
  1. स्वीकृत समाधान python3 के लिए सीधे काम नहीं करेगा - इसके लिए a की आवश्यकता होगी from functools import reduce
  2. इसके अलावा एक forलूप का उपयोग करने के लिए अधिक पायथोनिक लगता है । पायथन 3.0 में व्हाट्स न्यू से उद्धरण देखें ।

    निकाला हुआ reduce()। उपयोग करें functools.reduce()यदि आपको वास्तव में इसकी आवश्यकता है; हालांकि, 99 प्रतिशत समय एक स्पष्ट forलूप अधिक पठनीय है।

  3. इसके बाद, स्वीकृत समाधान गैर-मौजूदा नेस्टेड कुंजी (इसे वापस करता है KeyError) सेट नहीं करता है - एक समाधान के लिए @ eafit का उत्तर देखें

तो क्यों मूल्य पाने के लिए कोलेरोज के सवाल से सुझाई गई विधि का उपयोग न करें:

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

और मान सेट करने के लिए @ eafit के उत्तर से कोड:

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

दोनों सीधे अजगर 2 और 3 में काम करते हैं


6
मैं इस समाधान को पसंद करता हूं - लेकिन सावधान रहें। अगर मैं गलत नहीं हूं, क्योंकि पायथन डिक्शनरी अपरिवर्तनीय नहीं हैं getFromDict, तो कॉल करने वाले को नष्ट करने की क्षमता है dataDict। मैं copy.deepcopy(dataDict)पहले करूंगा । बेशक, (जैसा कि लिखा गया है) यह व्यवहार दूसरे फ़ंक्शन में वांछित है।
डायलन एफ

15

उपयोग कम करना चतुर है, लेकिन ओपी की निर्धारित विधि में समस्याएँ हो सकती हैं यदि मूल कुंजियाँ नेस्टेड डिक्शनरी में पहले से मौजूद न हों। चूंकि यह पहला SO पोस्ट है जिसे मैंने अपनी Google खोज में इस विषय के लिए देखा था, इसलिए मैं इसे थोड़ा बेहतर बनाना चाहूंगा।

में सेट विधि ( नेस्टेड पायथन डिक्शनरी में वैल्यू सेट करना, सूचकांकों और मूल्य की सूची दी गई है ) लापता अभिभावकीय कुंजी के लिए अधिक मजबूत लगती है। इसे कॉपी करने के लिए:

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

इसके अलावा, यह एक विधि के लिए सुविधाजनक हो सकता है जो मुख्य पेड़ को ट्रेस करता है और सभी पूर्ण कुंजी पथ प्राप्त करता है, जिसके लिए मैंने बनाया है:

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()], [])

इसका एक उपयोग नेस्टेड ट्री को एक पांडा डेटाफ़्रेम में परिवर्तित करना है, निम्न कोड का उपयोग करके (यह मानते हुए कि नेस्टेड शब्दकोश में सभी लीफ़्स में समान गहराई है)।

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)

मनमाने ढंग से 'कुंजियों' की तर्क लंबाई को 2 या अधिक में सीमित करें nested_set?
अलंकाल्वीति

10

यह लाइब्रेरी मददगार हो सकती है: https://github.com/akesterson/dpath-python

/ स्लेशेड / पाथ अला अलपथ के माध्यम से शब्दकोशों तक पहुंचने और खोजने के लिए एक अजगर पुस्तकालय

मूल रूप से यह आपको एक शब्दकोश से अधिक ग्लोब देता है जैसे कि यह एक फाइलसिस्टम था।


3

पुनरावर्ती कार्यों का उपयोग करने के बारे में कैसे?

मान प्राप्त करने के लिए:

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]

और एक मूल्य निर्धारित करने के लिए:

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

शुद्ध पायथन शैली, बिना किसी आयात के:

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)

उत्पादन

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

2

एक वैकल्पिक तरीका यदि आप त्रुटियों को नहीं उठाना चाहते हैं यदि कुंजी में से एक अनुपस्थित है (ताकि आपका मुख्य कोड बिना किसी रुकावट के चल सके):

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

इस मामले में, यदि कोई इनपुट कुंजी मौजूद नहीं है, तो कोई भी वापस नहीं किया जाता है, जिसका उपयोग वैकल्पिक कार्य करने के लिए आपके मुख्य कोड में चेक के रूप में किया जा सकता है।


1

हर बार जब आप एक मूल्य देखना चाहते हैं, तो एक प्रदर्शन करने के बजाय, आप कैसे शब्दकोश को एक बार समतल करते हैं, तो बस कुंजी को देखें। 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}

इस तरह आप केवल उन वस्तुओं का उपयोग कर सकते हैं flat_dict['b:v:y']जो आपको देंगे 1

और प्रत्येक लुकअप पर डिक्शनरी ट्रेस करने के बजाय, आप डिक्शनरी को चपटा करके और आउटपुट को सेव करके इसे गति दे सकते हैं, ताकि कोल्ड स्टार्ट से एक लुकअप का मतलब है चपटा डिक्शनरी को लोड करना और बिना किसी कुंजी / मान के लुकअप करना। ट्रेवर्सल।


1

इसे पुनरावृत्ति के साथ हल किया:

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

अपने उदाहरण का उपयोग करना:

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

कैसे के बारे में जाँच करें और फिर सभी अनुक्रमित दो बार प्रसंस्करण के बिना प्रमुख तत्व निर्धारित करें?

उपाय:

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

उदाहरण वर्कफ़्लो:

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()

परीक्षा

>>> 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

पार्टी के लिए बहुत देर हो चुकी है, लेकिन इस मामले में पोस्ट करने से भविष्य में किसी को मदद मिल सकती है। मेरे उपयोग के मामले के लिए, निम्नलिखित फ़ंक्शन ने सबसे अच्छा काम किया। किसी भी डेटा प्रकार को शब्दकोश से बाहर खींचने का काम करता है

तानाशाह हमारे मूल्य से युक्त शब्दकोश है

सूची हमारे मूल्य की ओर "कदम" की एक सूची है

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

नेस्टेड विशेषताओं को सेट करने और प्राप्त करने के लिए दो स्थिर तरीकों के लिए इन उत्तरों को देखना संतोषजनक है। ये समाधान घोंसले के पेड़ https://gist.github.com/hrldcpr/2012250 का उपयोग करने से बेहतर है

यहाँ मेरा कार्यान्वयन है।

उपयोग :

नेस्टेड विशेषता कॉल सेट करने के लिए sattr(my_dict, 1, 2, 3, 5) is equal to my_dict[1][2][3][4]=5

नेस्टेड विशेषता कॉल प्राप्त करने के लिए 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-benedictनेपथ्य का उपयोग करके नेस्टेड वस्तुओं तक पहुंचने के लिए उपयोग करें।

इसका उपयोग करके इसे स्थापित करें pip:

pip install python-benedict

फिर:

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'])

यहाँ पूर्ण प्रलेखन: https://github.com/fabiocaccamo/python-benedict


0

यदि आप भी नेस्टेड सूचियों और dicts सहित मनमाने ढंग से काम करने की क्षमता चाहते हैं, और अच्छी तरह से अमान्य लुकअप रास्तों को संभालते हैं, तो यहां मेरा समाधान है:

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

स्ट्रिंग्स को सुगम बनाने के लिए एक विधि:

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 और दूसरों के दृष्टिकोण का विस्तार करते हुए, ये कार्यात्मक (यानी, इनपुट को प्रभावित किए बिना डीपकोपी के माध्यम से संशोधित डेटा) सेटर और मैपर नेस्टेड के लिए काम करता है dictऔर list

सेटर:

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

मैपर:

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

आप evalअजगर में फ़ंक्शन का उपयोग कर सकते हैं ।

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

व्याख्या

आपके उदाहरण के लिए क्वेरी: maplist = ["b", "v", "y"]

nestqहो जाएगा "nest['b']['v']['y']"जहां nestनेस्टेड शब्दकोश है।

evalBuiltin समारोह को देखते हुए स्ट्रिंग निष्पादित करता है। हालांकि, evalफ़ंक्शन के उपयोग से उत्पन्न होने वाली संभावित कमजोरियों के बारे में सावधान रहना महत्वपूर्ण है । चर्चा यहाँ मिल सकती है:

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

में nested_parse()समारोह, मुझे यकीन है कि कोई भी बना दिया है __builtins__वैश्विक उपलब्ध है और केवल स्थानीय चर कि उपलब्ध है है nestशब्दकोश।


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.