programing

중첩된 키가 딕트에 존재하는지 확인하는 우아한 방법?

jooyons 2023. 10. 17. 20:15
반응형

중첩된 키가 딕트에 존재하는지 확인하는 우아한 방법?

각 레벨을 독립적으로 확인하지 않고 딕트에 저장된 키가 존재하는지 확인할 수 있는 더 가독성 있는 방법이 있습니까?

묻힌 개체에서 이 값을 가져와야 한다고 가정해 보겠습니다(Wikidata에서 가져온 예).

x = s['mainsnak']['datavalue']['value']['numeric-id']

이 작업이 런타임 오류로 끝나지 않도록 하려면 다음과 같이 모든 레벨을 확인해야 합니다.

if 'mainsnak' in s and 'datavalue' in s['mainsnak'] and 'value' in s['mainsnak']['datavalue'] and 'nurmeric-id' in s['mainsnak']['datavalue']['value']:
    x = s['mainsnak']['datavalue']['value']['numeric-id']

이 문제를 해결하기 위해 제가 생각할 수 있는 다른 방법은 이 문제를 해결하는 것입니다.try catch이런 단순한 작업을 하기에는 제가 느끼는 시공 또한 다소 어색합니다.

다음과 같은 것을 찾고 있습니다.

x = exists(s['mainsnak']['datavalue']['value']['numeric-id'])

돌아오는.True모든 레벨이 존재하는 경우.

간단히 말해서, 파이썬에서는 허락보다 용서를 구하는 것이 더 쉽다는 것을 믿어야 합니다.

try:
    x = s['mainsnak']['datavalue']['value']['numeric-id']
except KeyError:
    pass

정답은

중첩된 dict 키를 처리하는 방법은 다음과 같습니다.

def keys_exists(element, *keys):
    '''
    Check if *keys (nested) exists in `element` (dict).
    '''
    if not isinstance(element, dict):
        raise AttributeError('keys_exists() expects dict as first argument.')
    if len(keys) == 0:
        raise AttributeError('keys_exists() expects at least two arguments, one given.')

    _element = element
    for key in keys:
        try:
            _element = _element[key]
        except KeyError:
            return False
    return True

예:

data = {
    "spam": {
        "egg": {
            "bacon": "Well..",
            "sausages": "Spam egg sausages and spam",
            "spam": "does not have much spam in it"
        }
    }
}

print 'spam (exists): {}'.format(keys_exists(data, "spam"))
print 'spam > bacon (do not exists): {}'.format(keys_exists(data, "spam", "bacon"))
print 'spam > egg (exists): {}'.format(keys_exists(data, "spam", "egg"))
print 'spam > egg > bacon (exists): {}'.format(keys_exists(data, "spam", "egg", "bacon"))

출력:

spam (exists): True
spam > bacon (do not exists): False
spam > egg (exists): True
spam > egg > bacon (exists): True

그것은 주어진 상태로 순환합니다.element각 키를 주어진 순서대로 테스트합니다.

나는 이것을 모두 선호합니다.variable.get('key', {})EAFP를 따라하기 때문에 찾은 방법들입니다.

다음과 같이 호출되는 것을 제외한 기능:keys_exists(dict_element_to_test, 'key_level_0', 'key_level_1', 'key_level_n', ..). 요소와 키, 두 개 이상의 인수가 필요하지만 원하는 키 수를 추가할 수 있습니다.

맵 종류를 사용해야 할 경우 다음과 같은 작업을 수행할 수 있습니다.

expected_keys = ['spam', 'egg', 'bacon']
keys_exists(data, *expected_keys)

당신은 사용할 수 있습니다..get기본값 포함:

s.get('mainsnak', {}).get('datavalue', {}).get('value', {}).get('numeric-id')

그러나 이는 try/except를 사용하는 것보다 확실히 덜 명확합니다.

Python 3.8 +

dictionary = {
    "main_key": {
        "sub_key": "value",
    },
}

if sub_key_value := dictionary.get("main_key", {}).get("sub_key"):
    print(f"The key 'sub_key' exists in dictionary[main_key] and it's value is {sub_key_value}")
else:
    print("Key 'sub_key' doesn't exists or their value is Falsy")

추가의

하지만 중요한 해명.

이전 코드 블록에서는 키가 사전에 존재하지만 그 값도 Truthy임을 확인합니다.대부분의 경우, 이것이 사람들이 진정으로 원하는 것이고, 이것이 OP가 진정으로 원하는 것입니다.그러나 키가 존재하지만 그 값이 False이면 위의 코드 블록은 키가 존재하지 않는다고 알려주기 때문에 실제로 가장 "올바른" 답변은 아닙니다.

그래서 여기서 좀 더 정확한 답을 남깁니다.

dictionary = {
    "main_key": {
        "sub_key": False,
    },
}

if "sub_key" in dictionary.get("main_key", {}):
    print(f"The key 'sub_key' exists in dictionary[main_key] and it's value is {dictionary['main_key']['sub_key']}")
else:
    print("Key 'sub_key' doesn't exists")

시도해 보세요/그것을 하는 것이 가장 피톤적인 방법인 것 같습니다.
다음과 같은 재귀적 함수가 작동해야 합니다(키 중 하나가 dict에서 발견되지 않으면 None을 반환합니다).

def exists(obj, chain):
    _key = chain.pop(0)
    if _key in obj:
        return exists(obj[_key], chain) if chain else obj[_key]

myDict ={
    'mainsnak': {
        'datavalue': {
            'value': {
                'numeric-id': 1
            }
        }
    }
}

result = exists(myDict, ['mainsnak', 'datavalue', 'value', 'numeric-id'])
print(result)
>>> 1

를 사용하는 것을 제안합니다.python-benedict, 완전한 키 경로 지원과 많은 유틸리티 메소드를 가진 solid python dict 서브클래스.

기존 명령어만 사용하면 됩니다.

s = benedict(s)

이제 dict가 전체 키 경로를 지원하며 in 연산자를 사용하여 pythonic 방식으로 키가 존재하는지 확인할 수 있습니다.

if 'mainsnak.datavalue.value.numeric-id' in s:
    # do stuff

여기 라이브러리 저장소와 문서: https://github.com/fabiocaccamo/python-benedict

참고: 저는 이 프로젝트의 저자입니다.

사용가능pydash존재하는지 확인하려면: http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has

또는 값을 가져오거나(존재하지 않으면 반환하도록 기본값을 설정할 수도 있습니다):http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has

다음은 예입니다.

>>> get({'a': {'b': {'c': [1, 2, 3, 4]}}}, 'a.b.c[1]')
2

시도/제외 방법이 가장 깨끗하고 경쟁이 없습니다.그러나 내 IDE에서는 예외로 간주되므로 디버깅하는 동안 실행이 중단됩니다.

게다가 저는 예외를 메소드 내 제어문으로 사용하는 것을 좋아하지 않습니다. 이것은 근본적으로 시도/잡기에서 일어나는 일입니다.

다음은 재귀를 사용하지 않고 기본값을 지원하는 짧은 솔루션입니다.

def chained_dict_lookup(lookup_dict, keys, default=None):
    _current_level = lookup_dict
    for key in keys:
        if key in _current_level:
            _current_level = _current_level[key]
        else:
            return default
    return _current_level

수용된 답변은 좋은 답변이지만, 여기 또 다른 접근법이 있습니다.이것을 많이 해야 한다면 타이핑을 조금 덜 하고 눈에 조금 더 편하다고 생각합니다(저는 생각합니다).또한 다른 답변들처럼 추가적인 패키지 의존성을 요구하지 않습니다.성능을 비교하지 않았습니다.

import functools

def haskey(d, path):
    try:
        functools.reduce(lambda x, y: x[y], path.split("."), d)
        return True
    except KeyError:
        return False

# Throwing in this approach for nested get for the heck of it...
def getkey(d, path, *default):
    try:
        return functools.reduce(lambda x, y: x[y], path.split("."), d)
    except KeyError:
        if default:
            return default[0]
        raise

용도:

data = {
    "spam": {
        "egg": {
            "bacon": "Well..",
            "sausages": "Spam egg sausages and spam",
            "spam": "does not have much spam in it",
        }
    }
}

(Pdb) haskey(data, "spam")
True
(Pdb) haskey(data, "spamw")
False
(Pdb) haskey(data, "spam.egg")
True
(Pdb) haskey(data, "spam.egg3")
False
(Pdb) haskey(data, "spam.egg.bacon")
True

질문에 대한 답변에서 영감을 얻었습니다.

EDIT: 이것은 문자열 키에서만 작동한다고 지적된 코멘트.보다 일반적인 접근 방식은 반복 가능 경로 매개변수를 받아들이는 것입니다.

def haskey(d, path):
    try:
        functools.reduce(lambda x, y: x[y], path, d)
        return True
    except KeyError:
        return False

(Pdb) haskey(data, ["spam", "egg"])
True

저도 같은 문제가 있었는데 최근 파이썬 립이 떴습니다.

그렇다면 당신의 경우:

from dictor import dictor

x = dictor(s, 'mainsnak.datavalue.value.numeric-id')

개인 노트:
저는 'dictor'라는 이름을 좋아하지 않습니다. 왜냐하면 그것이 실제로 무엇을 하는지를 암시하지 않기 때문입니다.그래서 이렇게 쓰고 있습니다.

from dictor import dictor as extract
x = extract(s, 'mainsnak.datavalue.value.numeric-id')

이보다 더 나은 이름을 제시할 수 없습니다.extract. 좀 더 실용적인 네이밍을 제안해 주신다면 자유롭게 댓글을 달아주세요.safe_get,robust_get제 경우에 맞지 않다고 느껴졌습니다.

다른 방법:

def does_nested_key_exists(dictionary, nested_key):
    exists = nested_key in dictionary
    if not exists:
        for key, value in dictionary.items():
            if isinstance(value, dict):
                exists = exists or does_nested_key_exists(value, nested_key)
    return exists

선택한 답은 행복한 길에서는 잘 통하지만, 저에게는 몇 가지 분명한 문제가 있습니다.["스팸", "에그", "베이컨", "피자"]를 검색하면 "웰..." 색인을 시도하여 형식 오류가 발생합니다." pizza라는 문자열을 사용합니다.마찬가지로, 만약 피자를 2로 대체한다면, 그것은 지수 2를 "음..."에서 얻는 데 사용될 것입니다.

선택한 답변 출력 문제:

data = {
    "spam": {
        "egg": {
            "bacon": "Well..",
            "sausages": "Spam egg sausages and spam",
            "spam": "does not have much spam in it"
        }
    }
}

print(keys_exists(data, "spam", "egg", "bacon", "pizza"))
>> TypeError: string indices must be integers

print(keys_exists(data, "spam", "egg", "bacon", 2)))
>> l

저는 또한 트라이를 제외하고 사용하는 것은 우리가 너무 빨리 의지할 수 있는 목발이 될 수 있다고 생각합니다.이미 종류를 확인해야 할 것 같으니, 제외하고는 시도를 없애는 편이 좋을 것 같습니다.

해결책:

def dict_value_or_default(element, keys=[], default=Undefined):
    '''
    Check if keys (nested) exists in `element` (dict).
    Returns value if last key exists, else returns default value
    '''
    if not isinstance(element, dict):
        return default

    _element = element
    for key in keys:
        # Necessary to ensure _element is not a different indexable type (list, string, etc).  
        # get() would have the same issue if that method name was implemented by a different object
        if not isinstance(_element, dict) or key not in _element:
            return default

        _element = _element[key]
        
    return _element 

출력:

print(dict_value_or_default(data, ["spam", "egg", "bacon", "pizza"]))
>> INVALID

print(dict_value_or_default(data, ["spam", "egg", "bacon", 2]))
>> INVALID

print(dict_value_or_default(data, ["spam", "egg", "bacon"]))
>> "Well..."

@Aroust의 답변을 토대로 한 제 작은 토막글은 다음과 같습니다.

def exist(obj, *keys: str) -> bool:
    _obj = obj
    try:
        for key in keys:
            _obj = _obj[key]
    except (KeyError, TypeError):
        return False
    return True

if __name__ == '__main__':
    obj = {"mainsnak": {"datavalue": {"value": "A"}}}
    answer = exist(obj, "mainsnak", "datavalue", "value", "B")
    print(answer)

추가했습니다.TypeError왜냐하면 언제_objstr, int, 없음 또는 기타이므로 해당 오류를 발생시킵니다.

저는 기본적으로 위키데이터 API가 반환하는 JSON에 좌절했기 때문에 이런 경우에 대한 데이터 파싱 라이브러리를 작성했습니다.

그 도서관을 이용하면 이런 일을 할 수 있을 겁니다.

from dataknead import Knead

numid = Knead(s).query("mainsnak/datavalue/value/numeric-id").data()

if numid:
    # Do something with `numeric-id`

기본값과 함께 dict를 사용하는 것은 간결하며 연속 if 문을 사용하는 것보다 더 빨리 실행되는 것으로 보입니다.

직접 해보세요.

import timeit

timeit.timeit("'x' in {'a': {'x': {'y'}}}.get('a', {})")
# 0.2874350370002503

timeit.timeit("'a' in {'a': {'x': {'y'}}} and 'x' in {'a': {'x': {'y'}}}['a']")
# 0.3466246419993695

나는 이를 위해 편리한 도서관을 써왔습니다.

나는 dict의 ast를 반복하고 있으며 특정 키가 존재하는지 여부를 확인하려고 합니다.

이거 한번 확인해보세요.https://github.com/Agent-Hellboy/trace-dkey

개체 경로의 문자열 표현을 테스트하는 데 어려움을 겪을 수 있는 경우 다음과 같은 방법을 사용할 수 있습니다.

def exists(str):
    try:
        eval(str)
        return True
    except:
        return False

exists("lst['sublist']['item']")

키/nestedkey/value가 nested dict에 있는지 확인하기 위해 이것을 사용할 수 있습니다.

import yaml

#d - nested dictionary
if something in yaml.dump(d, default_flow_style=False):
    print(something, "is in", d)
else:
    print(something, "is not in", d)

좀 못생겼지만, 원라이너로 이를 달성하는 가장 간단한 방법.

d = {
     'mainsnak': {
             'datavalue': {
                     'value': {
                             'numeric-id': {
                              }
                      }
              }
     }
}

d.get('mainsnak',{}).get('datavalue',{}).get('value',{}).get('numeric-id')

좋은 답이 많습니다.이것에 대한 저의 겸손한 의견입니다.사전 배열에 대한 검사도 추가되었습니다.인수의 유효성을 확인하는 것이 아님을 유의하시기 바랍니다.위의 Arnot 부분 코드를 사용했습니다.데이터에서 배열이나 사전을 확인해야 하는 사용 사례가 생겨 이 답변을 추가했습니다.코드는 다음과 같습니다.

def keys_exists(element, *keys):
    '''
    Check if *keys (nested) exists in `element` (dict).
    '''
    
    retval=False
    if isinstance(element,dict):
        for key,value in element.items():
            for akey in keys:
                if element.get(akey) is not None:
                    return True
            if isinstance(value,dict) or isinstance(value,list):
                retval= keys_exists(value, *keys)
            
    elif isinstance(element, list):
        for val in element:
            if isinstance(val,dict) or isinstance(val,list):
                retval=keys_exists(val, *keys)

    return retval

언급URL : https://stackoverflow.com/questions/43491287/elegant-way-to-check-if-a-nested-key-exists-in-a-dict

반응형