检查python dict中是否存在嵌套键的优雅方法

时间:2017-04-19 09:11:46

标签: python variables object

是否有更可读的方法来检查埋在dict中的密钥是否存在而不是单独检查每个级别?

假设我需要在埋藏的对象中获取此值(示例来自维基数据):

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

13 个答案:

答案 0 :(得分:65)

简而言之,使用Python,您必须相信它是easier to ask for forgiveness than permission

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 type(element) is not 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)

答案 1 :(得分:10)

您可以使用.get默认值:

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

但这几乎肯定不如使用try / except。

答案 2 :(得分:5)

尝试/除了似乎是最诡计多端的方式。
以下递归函数应该有效(如果在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

答案 3 :(得分:5)

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

答案 4 :(得分:3)

您可以使用 onDrop(files) { let f = files.map(file => ({ name: file.name, preview: file.preview })); this.setState({ files: this.state.files.length > 0 ? [...this.state.files, ...f] : f, }); } 来检查是否存在:http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has

或获取值(您甚至可以设置默认值-如果不存在则返回):http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has

这里是一个例子:

pydash

答案 5 :(得分:3)

接受的 answer 是一个很好的方法,但这是另一种方法。如果你最终不得不经常这样做,那么打字会少一点,眼睛也容易一点(在我看来)。它也不需要像其他一些答案那样的任何额外的包依赖项。没有比较性能。

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

来自 this 问题答案的原始灵感。

答案 6 :(得分:2)

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

您只需要投射现有的字典:

s = benedict(s)

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

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

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

答案 7 :(得分: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`

答案 8 :(得分:1)

try / except方法是最干净的,没有竞争。但是,它也算作我的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

答案 9 :(得分:1)

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

{{1}}

答案 10 :(得分:1)

我遇到了同样的问题,最近出现了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不适合我的情况。

答案 11 :(得分: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

答案 12 :(得分:-1)

有很多很好的答案。这是我对它的谦虚看法。还添加了对字典数组的检查。请注意,我没有检查参数的有效性。我在上面使用了部分 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