web-dev-qa-db-ja.com

Pythonネストされた辞書の値を取得する安全なメソッド

ネストされた辞書があります。安全に値を取得する方法は1つだけですか?

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

あるいは、pythonには、ネストされた辞書用のget()のようなメソッドがありますか?

94
Arti

getを2回使用できます。

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

key1またはkey2が存在しない場合、これはNoneを返します。

example_dict['key1']が存在するが、dict(またはAttributeErrorメソッドを持つdictのようなオブジェクト)ではない場合、これはgetを発生させる可能性があることに注意してください。 try..exceptが添字付けできない場合、投稿したexample_dict['key1']コードは、代わりにTypeErrorを発生させます。

もう1つの違いは、try...exceptが最初の欠落キーの直後に短絡することです。 get呼び出しのチェーンはそうではありません。


構文example_dict['key1']['key2']を保持したいが、KeyErrorを発生させたくない場合は、 Hasher recipe を使用できます。

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

キーがない場合、これは空のHasherを返すことに注意してください。

Hasherdictのサブクラスであるため、dictを使用できるのとほぼ同じ方法でHasherを使用できます。すべて同じメソッドと構文が利用でき、Hashersは欠落しているキーを異なる方法で処理します。

次のように、通常のdictHasherに変換できます。

hasher = Hasher(example_dict)

Hasherを通常のdictに簡単に変換します。

regular_dict = dict(hasher)

別の方法は、ヘルパー関数のさを隠すことです。

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

したがって、コードの残りの部分は比較的読みやすいままにできます。

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

python reduce を使用することもできます。

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

ここでこれらすべての答えと私が行った小さな変更を組み合わせることで、この機能が役立つと思います。その安全、迅速、簡単なメンテナンス。

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)

例:

>>> 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
>>>
13
Yuda Prawira

Yoavの答え、さらに安全なアプローチの構築:

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

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

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)

使用法

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

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

再帰的なソリューション。最も効率的ではありませんが、他の例よりも少し読みやすく、functoolsに依存していません。

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

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

より洗練されたバージョン

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)
5
Pithikos

2番目のレベルのキーを取得するには、次のようにします。

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

Dictをラップし、キーに基づいて取得できる単純なクラス:

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

例えば:

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

キーが存在しない場合、デフォルトでNoneを返します。 FindDictラッパーのdefault=キーを使用して、これをオーバーライドできます。たとえば、 `:

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

属性を深く取得するために this を見た後、ドット表記を使用してネストされた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)
2
Donny Winston

私自身のコードで役に立つとわかったunutbuの答えの適応:

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

KeyErrorを回避するために、そのキーがまだない場合は、key1の辞書エントリを生成します。とにかくそのキーペアリングを含むネストされた辞書を作成したい場合は、これが最も簡単な解決策のように思えます。

0
GenesRus

キーの1つが欠落している場合にキーエラーを発生させるのは合理的なことなので、それをチェックして、それと同じように取得することさえできません。

def get_dict(d, kl):
  cur = d[kl[0]]
  return get_dict(cur, kl[1:]) if len(kl) > 1 else cur
0
Ben Usman