नेस्टेड शब्दकोश का मूल्य प्राप्त करने के लिए पायथन सुरक्षित विधि


145

मेरे पास एक नेस्टेड शब्दकोश है। क्या मूल्यों को सुरक्षित रूप से प्राप्त करने का केवल एक ही तरीका है?

try:
    example_dict['key1']['key2']
except KeyError:
    pass

या शायद अजगर के पास get()नेस्टेड शब्दकोश के लिए एक तरीका है ?


इन्हें भी देखें: stackoverflow.com/questions/14692690/…
dreftymac

1
आपके प्रश्न में कोड, मेरे विचार में, पहले से ही नेस्टेड मूल्यों को शब्दकोश से बाहर निकालने का सबसे अच्छा तरीका है। आप हमेशा except keyerror:क्लॉज में एक डिफ़ॉल्ट मान निर्दिष्ट कर सकते हैं ।
पीटर शोर्न

जवाबों:


281

आप getदो बार उपयोग कर सकते हैं :

example_dict.get('key1', {}).get('key2')

यह या Noneतो वापस आ जाएगा key1या key2मौजूद नहीं है।

ध्यान दें कि यह अभी भी एक बढ़ा सकता है AttributeError, तो example_dict['key1']मौजूद है, लेकिन एक dict (या एक dict जैसी वस्तु एक साथ नहीं है getविधि)। यदि try..exceptआपने पोस्ट किया है तो कोड TypeErrorइसके बजाय उठाएगा यदि example_dict['key1']सदस्यता समाप्त नहीं होगी।

एक और अंतर यह है कि try...exceptपहली लापता कुंजी के तुरंत बाद शॉर्ट-सर्किट। getकॉल की श्रृंखला नहीं है।


यदि आप वाक्य रचना को संरक्षित example_dict['key1']['key2']करना चाहते हैं , लेकिन यह कभी नहीं चाहते कि KeyErrors बढ़ाएं, तो आप हैश नुस्खा का उपयोग कर सकते हैं :

class Hasher(dict):
    # https://stackoverflow.com/a/3405143/190597
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

example_dict = Hasher()
print(example_dict['key1'])
# {}
print(example_dict['key1']['key2'])
# {}
print(type(example_dict['key1']['key2']))
# <class '__main__.Hasher'>

ध्यान दें कि जब कोई कुंजी गुम होती है तो यह खाली हाशर देता है।

चूँकि Hasherआप एक उपवर्ग का dictउपयोग कर सकते हैं, उसी तरह से आप एक हैशर का उपयोग कर सकते हैं dict। सभी समान तरीके और वाक्यविन्यास उपलब्ध हैं, हैशर्स केवल लापता कुंजी का अलग तरीके से इलाज करते हैं।

आप एक नियमित परिवर्तित कर सकते हैं dictएक में Hasherइस तरह:

hasher = Hasher(example_dict)

और एक Hasherको एक नियमित dictरूप से आसानी से रूपांतरित करें :

regular_dict = dict(hasher)

एक अन्य विकल्प कुरूपता को एक सहायक कार्य में छिपाना है:

def safeget(dct, *keys):
    for key in keys:
        try:
            dct = dct[key]
        except KeyError:
            return None
    return dct

तो आपके बाकी कोड अपेक्षाकृत पठनीय रह सकते हैं:

safeget(example_dict, 'key1', 'key2')

38
इसलिए, अजगर के पास इस मामले का सुंदर समाधान नहीं है ?:(
आरती

मैं एक समान कार्यान्वयन के साथ एक समस्या में भाग गया। यदि आपके पास d = {key1: कोई नहीं} है, तो पहले वाला कोई नहीं लौटेगा और फिर आपके पास कोई अपवाद नहीं होगा): मैं इसके लिए एक समाधान निकालने की कोशिश कर रहा हूं
Huercio

1
यह safegetविधि बहुत अधिक सुरक्षित नहीं है क्योंकि यह मूल शब्दकोश को अधिलेखित करती है, जिसका अर्थ है कि आप सुरक्षित रूप से ऐसा नहीं कर सकते हैं safeget(dct, 'a', 'b') or safeget(dct, 'a')
neverfox

4
@KurtBourbaki: dct = dct[key] reassigns के लिए एक नया मूल्य स्थानीय चर dct । यह मूल अधिदेश को उत्परिवर्तित नहीं करता है (इसलिए मूल अधिनायक अप्रभावित है safeget।) यदि, दूसरी ओर, dct[key] = ...का उपयोग किया गया होता, तो मूल अधिनायक को संशोधित किया गया होता। दूसरे शब्दों में, पायथन के नाम मूल्यों के लिए बाध्य हैं । एक नाम के लिए एक नए मूल्य का असाइनमेंट पुराने मूल्य को प्रभावित नहीं करता है (जब तक कि पुराने मूल्य के अधिक संदर्भ नहीं होते हैं, इस स्थिति में (
सीपीथॉन

1
safegetयदि कोई नेस्टेड तानाशाह की कुंजी मौजूद हो तो भी यह विधि विफल हो जाएगी, लेकिन मूल्य शून्य है। यह TypeError: 'NoneType' object is not subscriptableअगले पुनरावृत्ति में फेंक देगा
स्टेनली एफ।

60

आप अजगर को कम करने के लिए भी इस्तेमाल कर सकते हैं :

def deep_get(dictionary, *keys):
    return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)

5
बस यह उल्लेख करना चाहता था कि पाइलोन 3 में फंक्शनलबुल अब एक बिल्डिन नहीं है और इसे फंक्शंस से आयात करने की आवश्यकता है, जो इस दृष्टिकोण को थोड़ा कम सुरुचिपूर्ण बनाता है।
yoniLavi

3
इस टिप्पणी में थोड़ा सुधार: कमी अब Py3 में बिल्ट-इन नहीं है। लेकिन मैं यह नहीं देखता कि क्यों यह किसी भी कम सुरुचिपूर्ण बनाता है। यह है एक एक लाइनर के लिए यह कम उपयुक्त बनाते हैं, लेकिन एक एक लाइनर जा रहा है स्वचालित रूप से योग्य नहीं है या जा रहा है "सुरुचिपूर्ण" के रूप में अयोग्य घोषित कुछ।
पॉलएमसीजी

30

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

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

उदाहरण :

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>

1
जिनजा 2 टेम्प्लेट के लिए बिल्कुल सही
थॉमस

यह एक अच्छा समाधान है, हालांकि इसका एक नुकसान भी है: भले ही पहली कुंजी उपलब्ध न हो, या फ़ंक्शन के शब्दकोश तर्क के रूप में पारित मूल्य एक शब्दकोश नहीं है, फ़ंक्शन पहले तत्व से अंतिम एक तक जाएगा। मूल रूप से, यह सभी मामलों में ऐसा करता है।
आर्सेनी

1
deep_get({'a': 1}, "a.b")देता है, Noneलेकिन मैं एक अपवाद की तरह KeyErrorया कुछ और की उम्मीद करेंगे ।
स्टिकंडरफ्लो

@edityouprofile। उसके बाद आपको केवल वापसी मूल्य को बदलने के लिए छोटे से बदलाव Noneकरने की आवश्यकता हैRaise KeyError
Yuda Piraira

15

योव के जवाब पर निर्माण, एक भी सुरक्षित दृष्टिकोण:

def deep_get(dictionary, *keys):
    return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)

12

एक पुनरावर्ती समाधान। यह सबसे अधिक कुशल नहीं है, लेकिन मुझे यह अन्य उदाहरणों की तुलना में थोड़ा अधिक पठनीय लगता है और यह फंक्शंस पर निर्भर नहीं करता है।

def deep_get(d, keys):
    if not keys or d is None:
        return d
    return deep_get(d.get(keys[0]), keys[1:])

उदाहरण

d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code'])     # => 200
deep_get(d, ['garbage', 'status_code'])  # => None

एक अधिक पॉलिश संस्करण

def deep_get(d, keys, default=None):
    """
    Example:
        d = {'meta': {'status': 'OK', 'status_code': 200}}
        deep_get(d, ['meta', 'status_code'])          # => 200
        deep_get(d, ['garbage', 'status_code'])       # => None
        deep_get(d, ['meta', 'garbage'], default='-') # => '-'
    """
    assert type(keys) is list
    if d is None:
        return default
    if not keys:
        return d
    return deep_get(d.get(keys[0]), keys[1:], default)

8

जबकि कम दृष्टिकोण साफ और छोटा है, मुझे लगता है कि एक साधारण लूप ग्रो करना आसान है। मैंने एक डिफ़ॉल्ट पैरामीटर भी शामिल किया है।

def deep_get(_dict, keys, default=None):
    for key in keys:
        if isinstance(_dict, dict):
            _dict = _dict.get(key, default)
        else:
            return default
    return _dict

एक-लाइनर ने कैसे काम किया, यह समझने के लिए एक अभ्यास के रूप में, मैंने निम्नलिखित किया। लेकिन अंततः पाश दृष्टिकोण मेरे लिए अधिक सहज लगता है।

def deep_get(_dict, keys, default=None):

    def _reducer(d, key):
        if isinstance(d, dict):
            return d.get(key, default)
        return default

    return reduce(_reducer, keys, _dict)

प्रयोग

nested = {'a': {'b': {'c': 42}}}

print deep_get(nested, ['a', 'b'])
print deep_get(nested, ['a', 'b', 'z', 'z'], default='missing')

5

मैं आपको सुझाव देने की कोशिश करता हूं python-benedict

यह एक dictउपवर्ग है जो कीपथ समर्थन और बहुत कुछ प्रदान करता है।

स्थापना: pip install python-benedict

from benedict import benedict

example_dict = benedict(example_dict, keypath_separator='.')

अब आप नेपथ्य का उपयोग करके नेस्टेड मानों तक पहुँच सकते हैं :

val = example_dict['key1.key2']

# using 'get' method to avoid a possible KeyError:
val = example_dict.get('key1.key2')

या कुंजी सूची का उपयोग करके नेस्टेड मान एक्सेस करें :

val = example_dict['key1', 'key2']

# using get to avoid a possible KeyError:
val = example_dict.get(['key1', 'key2'])

यह अच्छी तरह से परीक्षण और GitHub पर खुला स्रोत है :

https://github.com/fabiocaccamo/python-benedict


@ perfecto25 धन्यवाद! मैं जल्द ही नई सुविधाएँ जारी
करूंगा

@ perfecto25 मैंने सूची इंडेक्स, जैसे उदाहरण के लिए समर्थन जोड़ा। d.get('a.b[0].c[-1]')
Fabio Caccamo

4

एक साधारण वर्ग जो एक तानाशाही को लपेट सकता है, और एक कुंजी के आधार पर पुनः प्राप्त कर सकता है:

class FindKey(dict):
    def get(self, path, default=None):
        keys = path.split(".")
        val = None

        for key in keys:
            if val:
                if isinstance(val, list):
                    val = [v.get(key, default) if v else None for v in val]
                else:
                    val = val.get(key, default)
            else:
                val = dict.get(self, key, default)

            if not val:
                break

        return val

उदाहरण के लिए:

person = {'person':{'name':{'first':'John'}}}
FindDict(person).get('person.name.first') # == 'John'

यदि कुंजी मौजूद नहीं है, तो यह Noneडिफ़ॉल्ट रूप से लौटता है। आप रैपर default=में एक कुंजी का उपयोग करके ओवरराइड कर सकते हैं FindDict- उदाहरण के लिए:

FindDict(person, default='').get('person.name.last') # == doesn't exist, so ''

3

दूसरे स्तर की कुंजी प्राप्त करने के लिए, आप यह कर सकते हैं:

key2_value = (example_dict.get('key1') or {}).get('key2')

2

गहराई से विशेषताएँ प्राप्त करने के लिए इसे देखने के बाद , मैंने dictडॉट नोटेशन का उपयोग करके सुरक्षित रूप से नेस्टेड मान प्राप्त करने के लिए निम्नलिखित बनाया । यह मेरे लिए काम करता है क्योंकि मेरी dictsdeserialized MongoDB ऑब्जेक्ट हैं, इसलिए मुझे पता है कि प्रमुख नाम .s शामिल नहीं हैं । इसके अलावा, मेरे संदर्भ में, मैं एक गलत फ़ॉलबैक मान ( None) निर्दिष्ट कर सकता हूं जो मेरे डेटा में नहीं है, इसलिए मैं फ़ंक्शन को कॉल करते समय कोशिश / छोड़कर पैटर्न से बच सकता हूं।

from functools import reduce # Python 3
def deepgetitem(obj, item, fallback=None):
    """Steps through an item chain to get the ultimate value.

    If ultimate value or path to value does not exist, does not raise
    an exception and instead returns `fallback`.

    >>> d = {'snl_final': {'about': {'_icsd': {'icsd_id': 1}}}}
    >>> deepgetitem(d, 'snl_final.about._icsd.icsd_id')
    1
    >>> deepgetitem(d, 'snl_final.about._sandbox.sbx_id')
    >>>
    """
    def getitem(obj, name):
        try:
            return obj[name]
        except (KeyError, TypeError):
            return fallback
    return reduce(getitem, item.split('.'), obj)

7
fallbackवास्तव में फ़ंक्शन में उपयोग नहीं किया जाता है।
153957

ध्यान दें कि यह कुंजी है कि एक को शामिल करने के लिए काम नहीं करता है.
जेडब्ल्यू।

जब हम obj [नाम] कहते हैं, तो obj.get (नाम, फ़ॉलबैक) क्यों न हो और ट्राइ-कैच से बचें (यदि आप ट्राई-कैच चाहते हैं, तो फ़ॉलबैक लौटें, कोई नहीं)
denvar

धन्यवाद @ 153957 मैंने ठीक कर दिया। और हां @JW, यह मेरे उपयोग के मामले के लिए काम करता है। आप sep=','दिए गए (sep, fallback) स्थितियों के लिए एक सामान्य शब्दकोष जोड़ सकते हैं । और @denvar, अगर कम करने के एक अनुक्रम के बाद objटाइप के बारे में कहा जाता है int, तो obj [नाम] एक टाइपऑयर्स उठाता है, जिसे मैं पकड़ता हूं। यदि मैंने इसके बजाय obj.get (नाम) या obj.get (नाम, फ़ॉलबैक) का उपयोग किया है, तो यह एक एट्रीब्यूट को बढ़ाएगा, इसलिए या तो मुझे पकड़ने की आवश्यकता होगी।
डॉनी विंस्टन

1

फिर भी एक ही कार्य के लिए एक अन्य फ़ंक्शन, यह देखने के लिए एक बूलियन भी लौटाता है कि कुंजी मिली थी या नहीं और कुछ अप्रत्याशित त्रुटियों को संभालती है।

'''
json : json to extract value from if exists
path : details.detail.first_name
            empty path represents root

returns a tuple (boolean, object)
        boolean : True if path exists, otherwise False
        object : the object if path exists otherwise None

'''
def get_json_value_at_path(json, path=None, default=None):

    if not bool(path):
        return True, json
    if type(json) is not dict :
        raise ValueError(f'json={json}, path={path} not supported, json must be a dict')
    if type(path) is not str and type(path) is not list:
        raise ValueError(f'path format {path} not supported, path can be a list of strings like [x,y,z] or a string like x.y.z')

    if type(path) is str:
        path = path.strip('.').split('.')
    key = path[0]
    if key in json.keys():
        return get_json_value_at_path(json[key], path[1:], default)
    else:
        return False, default

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

my_json = {'details' : {'first_name' : 'holla', 'last_name' : 'holla'}}
print(get_json_value_at_path(my_json, 'details.first_name', ''))
print(get_json_value_at_path(my_json, 'details.phone', ''))

(सच, 'होला')

(असत्य, '')



0

अनटुब के उत्तर का एक अनुकूलन जो मुझे अपने कोड में उपयोगी लगा:

example_dict.setdefaut('key1', {}).get('key2')

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


0

चूँकि कुंजी में त्रुटि होने पर, यदि कोई एक कुंजी गायब है, तो यह करना एक उचित बात है, हम इसके लिए जाँच भी नहीं कर सकते हैं और इसे निम्न प्रकार से प्राप्त कर सकते हैं:

def get_dict(d, kl):
  cur = d[kl[0]]
  return get_dict(cur, kl[1:]) if len(kl) > 1 else cur

0

reduceसूची के साथ काम करने के लिए दृष्टिकोण में थोड़ा सुधार । सरणी के बजाय डॉट्स द्वारा विभाजित स्ट्रिंग के रूप में डेटा पथ का उपयोग करना।

def deep_get(dictionary, path):
    keys = path.split('.')
    return reduce(lambda d, key: d[int(key)] if isinstance(d, list) else d.get(key) if d else None, keys, dictionary)

0

एक समाधान जो मैंने उपयोग किया है वह दोहरे पाने के समान है लेकिन अतिरिक्त तर्क के साथ टाइप करने की क्षमता से बचने के लिए यदि अन्य तर्क का उपयोग कर रहे हैं:

    value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value

हालाँकि, जितना अधिक शब्दकोश उतना अधिक बोझिल हो जाता है।


0

नेस्टेड शब्दकोश / JSON लुक्स के लिए, आप तानाशाह का उपयोग कर सकते हैं

पाइप स्थापित तानाशाह

स्पष्ट वस्तु

{
    "characters": {
        "Lonestar": {
            "id": 55923,
            "role": "renegade",
            "items": [
                "space winnebago",
                "leather jacket"
            ]
        },
        "Barfolomew": {
            "id": 55924,
            "role": "mawg",
            "items": [
                "peanut butter jar",
                "waggy tail"
            ]
        },
        "Dark Helmet": {
            "id": 99999,
            "role": "Good is dumb",
            "items": [
                "Shwartz",
                "helmet"
            ]
        },
        "Skroob": {
            "id": 12345,
            "role": "Spaceballs CEO",
            "items": [
                "luggage"
            ]
        }
    }
}

लोनेस्टार की वस्तुओं को प्राप्त करने के लिए, बस एक डॉट-अलग पथ प्रदान करें, अर्थात

import json
from dictor import dictor

with open('test.json') as data: 
    data = json.load(data)

print dictor(data, 'characters.Lonestar.items')

>> [u'space winnebago', u'leather jacket']

यदि पथ में मुख्य isnt मामले में आप फ़ॉलबैक मान प्रदान कर सकते हैं

वहाँ आप कर सकते हैं और अधिक विकल्प के रूप में अक्षर आवरण को अनदेखा कर सकते हैं और इसके अलावा अन्य वर्णों का उपयोग कर सकते हैं। ' पथ विभाजक के रूप में,

https://github.com/perfecto25/dictor


0

मैंने इस जवाब को थोड़ा बदल दिया। अगर हम संख्याओं के साथ सूची का उपयोग कर रहे हैं तो मैंने जाँच को जोड़ा। तो अब हम इसे जिस भी तरीके से उपयोग कर सकते हैं। deep_get(allTemp, [0], {})या deep_get(getMinimalTemp, [0, minimalTemperatureKey], 26)आदि

def deep_get(_dict, keys, default=None):
    def _reducer(d, key):
        if isinstance(d, dict):
            return d.get(key, default)
        if isinstance(d, list):
            return d[key] if len(d) > 0 else default
        return default
    return reduce(_reducer, keys, _dict)

0

पहले से ही बहुत सारे अच्छे उत्तर हैं, लेकिन मैं एक समारोह के साथ आया हूं जिसे जावास्क्रिप्ट भूमि में प्राप्त होने वाले आवास के समान कहा जाता है जो सूचकांक के माध्यम से सूचियों तक पहुंचने का समर्थन करता है:

def get(value, keys, default_value = None):
'''
    Useful for reaching into nested JSON like data
    Inspired by JavaScript lodash get and Clojure get-in etc.
'''
  if value is None or keys is None:
      return None
  path = keys.split('.') if isinstance(keys, str) else keys
  result = value
  def valid_index(key):
      return re.match('^([1-9][0-9]*|[0-9])$', key) and int(key) >= 0
  def is_dict_like(v):
      return hasattr(v, '__getitem__') and hasattr(v, '__contains__')
  for key in path:
      if isinstance(result, list) and valid_index(key) and int(key) < len(result):
          result = result[int(key)] if int(key) < len(result) else None
      elif is_dict_like(result) and key in result:
          result = result[key]
      else:
          result = default_value
          break
  return result

def test_get():
  assert get(None, ['foo']) == None
  assert get({'foo': 1}, None) == None
  assert get(None, None) == None
  assert get({'foo': 1}, []) == {'foo': 1}
  assert get({'foo': 1}, ['foo']) == 1
  assert get({'foo': 1}, ['bar']) == None
  assert get({'foo': 1}, ['bar'], 'the default') == 'the default'
  assert get({'foo': {'bar': 'hello'}}, ['foo', 'bar']) == 'hello'
  assert get({'foo': {'bar': 'hello'}}, 'foo.bar') == 'hello'
  assert get({'foo': [{'bar': 'hello'}]}, 'foo.0.bar') == 'hello'
  assert get({'foo': [{'bar': 'hello'}]}, 'foo.1') == None
  assert get({'foo': [{'bar': 'hello'}]}, 'foo.1.bar') == None
  assert get(['foo', 'bar'], '1') == 'bar'
  assert get(['foo', 'bar'], '2') == None
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.