Programming

중첩 된 사전의 값을 가져 오는 Python 안전한 방법

procodes 2020. 8. 8. 14:11
반응형

중첩 된 사전의 값을 가져 오는 Python 안전한 방법


중첩 된 사전이 있습니다. 가치를 안전하게 얻을 수있는 유일한 방법이 있습니까?

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

아니면 파이썬 get()에 중첩 사전 과 같은 방법이 있습니까?


get두 번 사용할 수 있습니다 .

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

None둘 중 하나가 key1있거나 key2존재하지 않으면 반환 됩니다 .

이것은 여전히 AttributeErrorif example_dict['key1']exist를 발생 시킬 수 있지만 dict (또는 get메서드가 있는 dict-like 객체)는 아닙니다 . try..except게시 코드 는 구독 할 수없는 TypeError경우 대신 a 를 발생 example_dict['key1']시킵니다.

또 다른 차이점은 try...except첫 번째 누락 된 키 직후 단락 이 발생한다는 것 입니다. get호출 체인은 그렇지 않습니다.


구문을 보존하고 example_dict['key1']['key2']싶지만 KeyErrors를 발생시키지 않도록하려면 Hasher 레시피를 사용할 수 있습니다 .

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를 반환합니다.

당신 Hasher의 서브 클래스 이기 때문에 dict당신이 사용할 수있는 것과 거의 같은 방식으로 Hasher를 사용할 수 있습니다 dict. 모든 동일한 방법과 구문을 사용할 수 있으며 Hasher는 누락 된 키를 다르게 취급합니다.

정규식 dict을 다음 Hasher과 같이 변환 할 수 있습니다 .

hasher = Hasher(example_dict)

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

python reduce를 사용할 수도 있습니다 .

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

이 모든 답변과 제가 만든 작은 변경 사항을 결합하면이 기능이 유용 할 것이라고 생각합니다. 안전하고 빠르고 쉽게 유지 보수가 가능합니다.

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

Yoav의 답변을 바탕으로 더욱 안전한 접근 방식 :

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

재귀 솔루션. 가장 효율적은 아니지만 다른 예제보다 좀 더 읽기 쉽고 functools에 의존하지 않습니다.

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)

감소 접근 방식은 깔끔하고 짧지 만 간단한 루프가 더 쉽게 찾을 수 있다고 생각합니다. 기본 매개 변수도 포함했습니다.

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

dict를 래핑하고 키를 기반으로 검색 할 수있는 간단한 클래스 :

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'

If the key doesn't exist, it returns None by default. You can override that using a default= key in the FindDict wrapper -- for example`:

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

for a second level key retrieving, you can do this:

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

After seeing this for deeply getting attributes, I made the following to safely get nested dict values using dot notation. This works for me because my dicts are deserialized MongoDB objects, so I know the key names don't contain .s. Also, in my context, I can specify a falsy fallback value (None) that I don't have in my data, so I can avoid the try/except pattern when calling the function.

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)

An adaptation of unutbu's answer that I found useful in my own code:

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

It generates a dictionary entry for key1 if it does not have that key already so that you avoid the KeyError. If you want to end up a nested dictionary that includes that key pairing anyway like I did, this seems like the easiest solution.


Since raising an key error if one of keys is missing is a reasonable thing to do, we can even not check for it and get it as single as that:

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

참고URL : https://stackoverflow.com/questions/25833613/python-safe-method-to-get-value-of-nested-dictionary

반응형