优雅的方法来检查字典中是否存在嵌套键?


78

是否有更可读的方法来检查是否存在嵌入字典的键而无需独立检查每个级别?

可以说我需要在埋藏的对象中获取此值(示例取自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如果所有级别都存在,则返回。

Answers:


126

简而言之,使用Python,您必须相信请求宽恕比允许许可容易

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)

是的,如上所述,这是一个有效的解决方案。但是,想象一个函数访问该变量的次数大约是该变量的10倍,那么所有try except语句都会显得有些肿。
loomi

@loomi您可以使此try-except逻辑成为一个小函数,每次都只需调用此函数
Chris_Rands

@loomi将其包装在函数中。
juanpa.arrivillaga

1
“用Python,您必须相信两个词,要比请求许可更容易获得宽恕”,远远超过了两个词。
user2357112支持Monica17年

1
伟大的答案,但有一点应改为:if type(element) is not dictif not isinstance(element, dict)。这样,它也适用于OrderedDict之类的类型。
Maxxim

16

您可以使用.get默认值:

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

但这几乎可以肯定不如使用try / except清楚。


1
不管您提供的最后一个get为默认值,都可能恰好是的值s['mainsnak']['datavalue']['value']['numeric-id']
timgeb

4
我已经使用了很多这种构造,并且刚刚被这个镜头所吸引。使用上面的示例时要小心,因为如果“ getted”元素实际上存在并且不是dict(或您可以在其上调用的对象get)(我的情况是这样),那么它将最终以'NoneType' object has no attribute 'get'您拥有的任何类型结束。

9

尝试/例外似乎是最Python化的方法。
以下递归函数应该起作用(如果在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

您将如何处理数组,例如如果“值”是“数字ID”数组的结果=存在(myDict,[“ mainsnak”,“数据值”,“值[0]”,“数字ID”] )?
Dss

@Maurice Meyer:如果存在“ mainsnak2”,“ mainsnak3”等(如“ mainsnak”,内部字典保持不变)怎么办?在那种情况下,我们可以检查所有“ mainsnak”,“ mainsnak2”和“ mainsnak3”中是否存在“ datavalue”?
StackGuru


4

尝试/例外方式是最干净的,没有竞争。但是,它也算作我的IDE中的异常,这会在调试时停止执行。

此外,我不喜欢将异常用作方法内控制语句,这实际上就是try / catch发生的事情。

这是一个不使用递归并且支持默认值的简短解决方案:

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

我喜欢这种解决方案:) ...在这里只是个注释。在某些时候current_level[key]可以指向一个值,而不是一个内在的字典。因此,使用此方法的任何人都应注意检查它current_level是否不是字符串,浮点数或其他内容。
乔丹·辛巴

4

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

SyntaxError:如果key_exists:= dictionary.get(“ key_1”,{})。get(“ key_2”)时语法无效:
aysh

@aysh这是Python 3.8示例
Lucas Vazquez

4

我建议您使用python-benedict,具有完整keypath支持和许多实用程序方法的可靠python dict子类。

您只需要转换现有的字典即可:

s = benedict(s)

现在,您的字典具有完整的密钥路径支持,您可以使用in运算符检查密钥是否以pythonic方式存在:

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

这里是库存储库和文档:https : //github.com/fabiocaccamo/python-benedict

注意:我是这个专案的作者


2

我有同样的问题,最近的python lib弹出了:
https : //pypi.org/project/dictor/
https://github.com/perfecto25/dictor

因此,在您的情况下:

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_getrobust_get不适合我的情况。


1

我写了一个数据解析库dataknead来处理这种情况,基本上是因为我对Wikidata API返回的JSON感到沮丧。

有了那个图书馆,你可以做这样的事情

from dataknead import Knead

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

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

1

如果您可以测试对象路径的字符串表示形式,那么此方法可能对您有用:

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

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

但在此功能范围内,“ lst”未定义
Dss

1

其他方式:

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

什么是dos_nested_key_exists(value,nested_key)
aysh,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.