获取嵌套字典值的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
Python safe method to get value of nested dictionary
提问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如果存在key1或key2不存在,这将返回。
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
回答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)

