获取嵌套字典值的Python安全方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25833613/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 23:42:43  来源:igfitidea点击:

Python safe method to get value of nested dictionary

pythondictionarymethodsexcept

提问by Arti

I have a nested dictionary. Is there only one way to get values out safely?

我有一个嵌套字典。只有一种方法可以安全地获取值吗?

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

Or maybe python has a method like get()for nested dictionary ?

或者 python 有一个类似于get()嵌套字典的方法?

采纳答案by unutbu

You could use gettwice:

您可以使用get两次:

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

This will return Noneif either key1or key2does not exist.

None如果存在key1key2不存在,这将返回。

Note that this could still raise an AttributeErrorif example_dict['key1']exists but is not a dict (or a dict-like object with a getmethod). The try..exceptcode you posted would raise a TypeErrorinstead if example_dict['key1']is unsubscriptable.

请注意,这仍然可能引发AttributeErrorifexample_dict['key1']存在但不是 dict (或带有get方法的类似 dict 的对象)。如果不可订阅,try..except您发布的代码将引发 a 。TypeErrorexample_dict['key1']

Another difference is that the try...exceptshort-circuits immediately after the first missing key. The chain of getcalls does not.

另一个区别是try...except在第一个丢失的键之后立即短路。get调用链没有。



If you wish to preserve the syntax, example_dict['key1']['key2']but do not want it to ever raise KeyErrors, then you could use the Hasher recipe:

如果您希望保留语法,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'>

Note that this returns an empty Hasher when a key is missing.

请注意,当缺少键时,这将返回一个空的哈希器。

Since Hasheris a subclass of dictyou can use a Hasher in much the same way you could use a dict. All the same methods and syntax is available, Hashers just treat missing keys differently.

由于Hasher是一个子类,dict您可以像使用dict. 所有相同的方法和语法都可用,Hashers 只是以不同的方式处理丢失的键。

You can convert a regular dictinto a Hasherlike this:

您可以将常规dict转换为Hasher这样的:

hasher = Hasher(example_dict)

and convert a Hasherto a regular dictjust as easily:

并同样轻松地将 a 转换Hasher为常规dict

regular_dict = dict(hasher)


Another alternative is to hide the ugliness in a helper function:

另一种选择是在辅助函数中隐藏丑陋之处:

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

So the rest of your code can stay relatively readable:

因此,您的其余代码可以保持相对可读性:

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

回答by Yoav T

You could also use python reduce:

你也可以使用 python reduce

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

回答by Donny Winston

After seeing thisfor deeply getting attributes, I made the following to safely get nested dictvalues using dot notation. This works for me because my dictsare 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.

在看到这一点以深入获取属性后,我做了以下操作以dict使用点表示法安全地获取嵌套值。这对我dicts有用,因为我是反序列化的 MongoDB 对象,所以我知道键名不包含.s。此外,在我的上下文中,我可以指定None我的数据中没有的虚假回退值 ( ),因此我可以在调用函数时避免使用 try/except 模式。

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)

回答by Jose Alban

Building up on Yoav's answer, an even safer approach:

以 Yoav 的回答为基础,一种更安全的方法:

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

回答by zzz

While the reduce approach is neat and short, I think a simple loop is easier to grok. I've also included a default parameter.

虽然reduce 方法简洁而简短,但我认为简单的循环更容易理解。我还包含了一个默认参数。

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

As an exercise to understand how the reduce one-liner worked, I did the following. But ultimately the loop approach seems more intuitive to me.

作为了解 reduce one-liner 如何工作的练习,我做了以下工作。但最终循环方法对我来说似乎更直观。

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)

Usage

用法

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

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

回答by Jacob CUI

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

对于二级密钥检索,您可以这样做:

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

回答by Lee Benson

A simple class that can wrap a dict, and retrieve based on a key:

一个简单的类,可以包装一个字典,并根据一个键进行检索:

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

For example:

例如:

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

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

如果密钥不存在,则None默认返回。您可以使用包装器中的default=键覆盖它FindDict——例如:

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

回答by Yuda Prawira

By combining all of these answer here and small changes that I made, I think this function would be useful. its safe, quick, easily maintainable.

通过将所有这些答案和我所做的小改动结合起来,我认为这个功能会很有用。它安全、快速、易于维护。

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)

Example :

例子 :

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

回答by GenesRus

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

我在自己的代码中发现有用的 unutbu 答案的改编版:

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.

如果 key1 没有该键,它会为 key1 生成一个字典条目,以便您避免 KeyError。如果你想像我一样最终得到一个包含该键配对的嵌套字典,这似乎是最简单的解决方案。

回答by Pithikos

A recursive solution. It's not the most efficient but I find it a bit more readable than the other examples and it doesn't rely on functools.

递归解决方案。它不是最有效的,但我发现它比其他示例更具可读性,并且它不依赖于 functools。

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

Example

例子

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


A more polished version

更精致的版本

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)