web-dev-qa-db-ja.com

ネストされたキーがpython dictに存在するかどうかを確認するエレガントな方法

各レベルを個別にチェックせずに、辞書に埋め込まれたキーが存在するかどうかを確認するより読みやすい方法はありますか?

埋められたオブジェクトにこの値を取得する必要があるとしましょう(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']

これを解決するために考えられるもう1つの方法は、これをtry catchコンストラクトにラップすることです。

私は次のようなものを探しています:

x = exists(s['mainsnak']['datavalue']['value']['numeric-id'])

すべてのレベルが存在する場合、Trueを返します。

43
loomi

簡単に言うと、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をループします。

EAFP に続くため、私が見つけたすべてのvariable.get('key', {})メソッドよりもこれを好む。

keys_exists(dict_element_to_test, 'key_level_0', 'key_level_1', 'key_level_n', ..)のように呼び出されることを除く関数。少なくとも2つの引数、要素と1つのキーが必要ですが、必要なキーの数を追加できます。

ある種のマップを使用する必要がある場合、次のようなことができます。

expected_keys = ['spam', 'Egg', 'bacon']
keys_exists(data, *expected_keys)
76
Arount

デフォルトで.getを使用できます:

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

しかし、これはほぼ確実にtry/exceptを使用する場合ほど明確ではありません。

11
Daniel Roseman

Try/exceptは、これを行うための最もPython的な方法のようです。
次の再帰関数が機能するはずです(キーの1つが辞書に見つからなかった場合は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
6
Maurice Meyer

python-benedict、完全なキーパスサポートおよび多くのユーティリティメソッドを備えた堅実なpython dictサブクラスを使用することをお勧めします。

既存の辞書をキャストするだけです:

s = benedict(s)

これで、dictは完全なキーパスをサポートし、キーがPythonの方法で存在するかどうかを確認できますin演算子を使用

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

ライブラリリポジトリとドキュメント: https://github.com/fabiocaccamo/python-benedict

4
Fabio Caccamo

pydashを使用して、存在するかどうかを確認できます。 http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has

または、値を取得します(デフォルトを設定することもできます-存在しない場合は戻ります): http://pydash.readthedocs.io/en/latest/api.html#pydash.objects.has

以下に例を示します。

>>> get({'a': {'b': {'c': [1, 2, 3, 4]}}}, 'a.b.c[1]')
2
3
Alexander

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

オブジェクトパスの文字列表現のテストに苦しむことができる場合、このアプローチはあなたのために働くかもしれません:

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

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

このような場合のために 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
Husky